What happens while waiting on a Task's Result?

39,892

Solution 1

In Windows, all I/O is asynchronous. Synchronous APIs are just a convenient abstraction.

So, when you use HttpWebRequest.GetResponse, what actually happens is the I/O is started (asynchronously), and the calling thread (synchronously) blocks, waiting for it to complete.

Similarly, when you use HttpClient.PostAsync(..).Result, the I/O is started (asynchronously), and the calling thread (synchronously) blocks, waiting for it to complete.

I usually recommend people use await rather than Task.Result or Task.Wait for the following reasons:

  1. If you block on a Task that is the result of an async method, you can easily get into a deadlock situation.
  2. Task.Result and Task.Wait wrap any exceptions in an AggregateException (because those APIs are holdovers from the TPL). So error handling is more complex.

However, if you're aware of these limitations, there are some situations where blocking on a Task can be useful (e.g., in a Console application's Main).

Solution 2

Capturing the result of a task blocks the current thread. There is no point in using a async version of a method in this case. Post() and PostAsync().Result will both block.

If you want to make use of concurrency, you should write it as such:

async Task PostContent()
{
  var client = new HttpClient();
  Task t = await client.PostAsync("http://someservice/", someContent);
  //code after this line will execute when the PostAsync completes.
  return t;
}

Since PostContent() itself returns a Task, the method calling it should also await.

async void ProcessResult()
{
   var result = await PostContent(); 
   //Do work with the result when the result is ready 
}

For instance, if you call ProcessResult() in a button click handler, you see that the UI is still responsive, other controls still function.

Share:
39,892

Related videos on Youtube

scottt732
Author by

scottt732

Software Engineer & Site Reliability Engineer

Updated on July 09, 2022

Comments

  • scottt732
    scottt732 almost 2 years

    I'm using the HttpClient to post data to a remote service in a .NET 4.0 project. I'm not concerned with this operation blocking, so I figured I could skip ContinueWith or async/await and use Result.

    While debugging, I ran into an issue where the remote server wasn't responsive. As I stepped through the code, it seemed like my code just stopped running on the third line... the current stack pointer line stopped being highlighted yellow, and didn't advance to the next line. It just disappeared. It took me a while to realize that I should wait for the request to timeout.

    var client = new HttpClient();
    var task = client.PostAsync("http://someservice/", someContent);
    var response = task.Result;
    

    My understanding was that calling Result on the Task caused the code to execute synchronously, to behave more like this (I know there is no Post method in the HttpClient):

    var client = new HttpClient();
    var response = client.Post("http://someservice/", someContent);
    

    I'm not sure this is a bad thing, I'm just trying to get my head around it. Is it really true that by virtue of the fact that the HttpClient is returning Tasks instead of the results directly, my application is automatically taking advantage of asynchrony even when I think I'm avoiding it?

    • Simon Ejsing
      Simon Ejsing over 11 years
      the docs says: "The get accessor for this property ensures that the asynchronous operation is complete before returning." so you would be correct in assuming that. However, maybe you run into the case where the Task throws an exception because the operation failed.
    • scottt732
      scottt732 over 11 years
      I guess what threw me was that the stack pointer disappeared. I didn't realize that if I waited long enough, the blocking Result call would exception out. It just looked as if the code stopped executing half way through the method. Maybe this should be a VS Connect type issue to ask for some UI cue that there is an async operation pending on the third line. I think the debugger even brought me back to the calling class.
    • Kiquenet
      Kiquenet over 10 years
      any final solution with full source code?
  • Vukoje
    Vukoje almost 9 years
    So the conclusion is not to use HttpClient if your code is synchronous? Isn't this a bit of a .NET design smell? We cannot consume Async methods unless we make whole call stack Async. I would expect that waiting on Async methods to finish in order to finalize synchronous opration would be a most common usage of async await.
  • Stephen Cleary
    Stephen Cleary almost 9 years
    @Vukoje: Naturally-asynchronous operations (including all I/O) should be represented by asynchronous APIs. The proper way to consume them is asynchronously, not synchronously. If a dev "needs" to consume an asynchronous API synchronously, then that's always indicative of a misdesign in the consuming application (sometimes made unavoidable due to a misdesign in a framework, but usually just a misdesign in the application).
  • Vukoje
    Vukoje almost 9 years
    Thanks for clarification! Just to be sure I understood fully, if I have WebAPI method that I don't wanna make async I shouldn't use HttpClient.PostAsync? What if in that WebAPI method (not async) I wanna have two parallel HTTP posts? In that case should I use two PostAsync() calls in combination with Task.WaitAll or that approach is also not good.
  • Stephen Cleary
    Stephen Cleary almost 9 years
    @Vukoje: If your WebAPI method is doing HTTP requests, it should be async. If you choose to make it synchronous for whatever reason, then you could wrap each (synchronous) POST call in a Task.Run.
  • Vukoje
    Vukoje almost 9 years
    Thank you very much for your effort, it really helped me a lot! I still don't understand in general why would I make WebApi method async. It adds some complexity and the only benefit I found by reading is that it might benefit web server's response time under heavy load.
  • Stephen Cleary
    Stephen Cleary almost 9 years
    @Vukoje: Yes, scalability is the primary benefit. I also find it more maintainable, since asynchronous methods are asynchronous.