We have a .NET application that uses an HTTP based API where we POST
a request to a third party HTTP endpoint (that is not under our control) and it calls us back at a later time on an HTTP endpoint that we give it; something like:
WebRequest request = WebRequest.Create(urlToMethod);
request.Method = @"POST";
request.Headers.Add(@"Callback", "http://ourserver?id="+id );
We make thousands upon thousands of these calls and so we'd like to be as effecient as possible (in terms of speed/memory/threads etc.)
As far as the callback code is concerned, we have a type that acts as a listener; this is how we start it up:
_httpListener = new HttpListener();
_httpListener.Prefixes.Add(ourServer);
_httpListener.Start();
_httpListener.BeginGetContext(callback, null);
When the server calls us back, it hits our callback
method which looks something like this:
HttpListenerContext context = _httpListener.EndGetContext(result);
HttpListenerResponse httpListenerResponse = context.Response;
httpListenerResponse.StatusCode = 200;
httpListenerResponse.ContentLength64 = _acknowledgementBytes.Length;
var output = httpListenerResponse.OutputStream;
output.Write(_acknowledgementBytes, 0, _acknowledgementBytes.Length);
context.Response.Close();
var handler = ResponseReceived;
if (handler != null)
{
handler(this, someData);
}
So we have a single instance of this listener (_which internally uses HttpListener
) and for every response it gets, it informs all of the subscribers on the ResponseReceived
event.
The subscribers (possibly hundreds of them) only care about data associated with their particular id
. The subscriber
s look something like:
_matchingResponseReceived = new ManualResetEventSlim(false);
_listener.WhenResponseReceived += checkTheIdOfWhatWeGetAndSetTheEventIfItMatches;
postTheMessage();
_matchingResponseReceived.Wait(someTimeout);
It's that last line that's bugging me. We post the message but then block the whole thread waiting for the Listener
to get a response and call our event handler. We'd like to use Task
s but doesn't seem like it'll give us much if we're blocking a whole thread waiting for the callback.
Is there a better (more TPL friendly) way of achieving this so that no threads are blocked and we get fire off more requests simultaneously?
async
-await
together withTaskCompletionSource
pretty much were made for this.The sender side creates a
TaskCompletionSource
, adds it to a dictionary (with key being the id of the request), makes the request and returns theTaskCompletionSource
'sTask
.The receiver then looks into the dictionary to find the right
TaskCompletionSource
, removes it from there and sets its result.The caller of the sender method will
await
the returnedTask
, which will asynchronously wait for the receiver to process the callback call.In code, it could look something like this: