3

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?

Paul Hiles
  • 9,558
  • 7
  • 51
  • 76

2 Answers2

3

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...

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks but I am using ContinueWith syntax (only .NET 4). I know that the standard advice is to always do it asynchronously via ContinueWith but was curious about the problem and suggested solution in the first link from my original post where a high scoring MVP suggested NOT using ContinueWith and just accessing directly. – Paul Hiles Apr 10 '13 at 15:01
  • ASP.NET WebAPI was designed from the ground up to properly work with `async` and `await`. So the most natural solution is `await`. I know some people have gotten WebAPI to work on .NET 4, but I would be extremely cautious about doing anything `async` in that scenario, since ASP.NET got some significant changes in .NET 4.5 to support `async` code. – Stephen Cleary Apr 10 '13 at 15:15
1

Calling .Result synchronously blocks the thread until the task completes. This is not a good thing because the thread is just spinning waiting for the async operation to complete.

You should prefer using ContinueWith or even better, async and await if you're using .NET 4.5. Here's a good resource for you to learn more about that:

http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

Youssef Moussaoui
  • 12,187
  • 2
  • 41
  • 37
  • Thanks. How does this fit in with the advice from the MVP in the first link I gave where he says "The problem is that when you use ContinueWith which may run inside a new Task which run concurrently with other handler(like HttpControllerDispatcher)"? – Paul Hiles Apr 04 '13 at 20:09