Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use ContinueWith() after ReadAsAsync in DelegatingHandler

Say I have a DelegatingHandler that I am using to log api requests. I want to access the request and perhaps response content in order to save to a db.

I can access directly using:

var requestBody = request.Content.ReadAsStringAsync().Result;

which I see in a lot of examples. It is also suggested to use it here by somebody that appears to know what they are talking about. Incidentally, the suggestion was made because the poster was originally using ContinueWith but was getting intermittent issues.

In other places, here, the author explicitly says not to do this as it can cause deadlocks and recommends using ContinueWith instead. This information comes directly from the ASP.net team apparently.

So I am a little confused. The 2 scenarios looks very similar in my eyes so appear to be conflicting.

Which should I be using?

like image 676
Paul Hiles Avatar asked Jan 21 '26 04:01

Paul Hiles


1 Answers

You should use await.

One of the problems with Result is that it can cause deadlocks, as I describe on my blog.

The problem with ConfigureAwait is that by default it will execute the continuation on the thread pool, outside of the HTTP request context.

You can get working solutions with either of these approaches (though as Youssef points out, Result will still have sub-optimal performance), but why bother? await does it all for you: no deadlocks, optimal threading, and resuming within the HTTP request context.

var requestBody = await request.Content.ReadAsStringAsync();

Edit for .NET 4.0: First, I strongly recommend upgrading to .NET 4.5. The ASP.NET runtime was enhanced in .NET 4.5 to properly handle Task-based async operations. So the code below may or may not work if you install WebAPI into a .NET 4.0 project.

That said, if you want to try properly using the old-school ContinueWith, something like this should work:

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request,
    CancellationToken cancellationToken)
{
  var context = TaskScheduler.FromCurrentSynchronizationContext();
  var tcs = new TaskCompletionSource<HttpResponseMessage>();
  HttpResponseMessage ret;

  try
  {
    ... // logic before you need the context
  }
  catch (Exception ex)
  {
    tcs.TrySetException(ex);
    return tcs.Task;
  }

  request.Content.ReadAsStringAsync().ContinueWith(t =>
  {
    if (t.Exception != null)
    {
      tcs.TrySetException(t.Exception.InnerException);
      return;
    }

    var content = t.Result;
    try
    {
      ... // logic after you have the context
    }
    catch (Exception ex)
    {
      tcs.TrySetException(ex);
    }
    tcs.TrySetResult(ret);
  }, context);
  return tcs.Task;
}

And now it becomes clear why await is so much better...

like image 54
Stephen Cleary Avatar answered Jan 23 '26 09:01

Stephen Cleary



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!