Skip to content

Instantly share code, notes, and snippets.

@samoshkin
Created October 31, 2011 21:50
Show Gist options
  • Save samoshkin/1329102 to your computer and use it in GitHub Desktop.
Save samoshkin/1329102 to your computer and use it in GitHub Desktop.
WCF-Web-API/SendAsync-nightmare
protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException("request", SR.net_http_handler_norequest);
}
// ProcessRequest() and ProcessResponse() are supposed to be fast, so we call ProcessRequest() on the same
// thread SendAsync() was invoked to avoid context switches. However, if ProcessRequest() throws, we have
// to catch the exception since the caller doesn't expect exceptions when calling SendAsync(): The
// expectation is that the returned task will get faulted on errors, but the async call to SendAsync()
// should complete.
TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
try
{
HttpRequestMessage newRequestMessage = ProcessRequest(request, cancellationToken);
Task<HttpResponseMessage> sendAsyncTask = base.SendAsync(newRequestMessage, cancellationToken);
// We schedule a continuation task once the inner handler completes in order to trigger the response
// processing method. ProcessResponse() is only called if the task wasn't canceled before.
sendAsyncTask.ContinueWith(task =>
{
if (task.IsFaulted)
{
tcs.TrySetException(task.Exception.GetBaseException());
return;
}
if (task.IsCanceled)
{
tcs.TrySetCanceled();
return;
}
if (task.Result == null)
{
tcs.TrySetException(new InvalidOperationException(SR.net_http_handler_noresponse));
return;
}
try
{
HttpResponseMessage responseMessage = ProcessResponse(task.Result, cancellationToken);
tcs.TrySetResult(responseMessage);
}
catch (OperationCanceledException e)
{
// If ProcessResponse() throws an OperationCanceledException check wheter it is related to
// the cancellation token we received from the user. If so, cancel the Task.
HandleCanceledOperations(cancellationToken, tcs, e);
}
catch (Exception e)
{
tcs.TrySetException(e);
}
// We don't pass the cancellation token to the continuation task, since we want to get called even
// if the operation was canceled: We'll set the Task returned to the user to canceled. Passing the
// cancellation token here would result in the continuation task to not be called at all. I.e. we
// would never complete the task returned to the caller of SendAsync().
}, TaskContinuationOptions.ExecuteSynchronously);
}
catch (OperationCanceledException e)
{
HandleCanceledOperations(cancellationToken, tcs, e);
}
catch (Exception e)
{
tcs.TrySetException(e);
}
return tcs.Task;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment