best way to use the nice .net 4.5 HttpClient synchronously

18,830

Solution 1

From http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx:

return Task.Run(() => Client.PostAsync()).Result;

Solution 2

@Noseratio - of course I am concerned about deadlocks :-)

You only would be concerned about deadlocks if you're on a thread with synchronization context.

That would normally be either the main thread of a UI application (client-side), or random ASP.NET thread processing an HTTP request (server-side). In either case you really shouldn't be blocking it.

The accepted answer might help mitigating the deadlock, but blocking like this would still hurt the end user experience of your UI app (the UI would be frozen), or the server-side app scalability (a thread wasted with blocking, while it could be serving another request). Just don't block and use async/await all the way through.

You mentioned "deep inside a server" but provided no details on what kind of server-side app is that. Most of the modern server-side frameworks have a good plumbing for async/await, so it shouldn't be a problem embracing it.

Updated to address the comment:

I still dont like the fact that the same code written in different places will deadlock. I would expect the wait call in a deadlock-prone environment to throw

This is not particularly a problem of async/await but is that of synchronization context concept in general, when it's used with blocking code. Here is the deadlock in a nutshell:

private void Form1_Load(object sender, EventArgs e)
{
    var mre = new System.Threading.ManualResetEvent(initialState: false);
    System.Threading.SynchronizationContext.Current.Post(_ => 
        mre.Set(), null);
    mre.WaitOne();
    MessageBox.Show("We never get here");
}

In theory, it might be possible to try to mitigate potential deadlocks inside SynchronizationContext.Post, say by checking Thread.ThreadState == System.Threading.ThreadState.WaitSleepJoin. That would not however be a 100% reliable solution.

Solution 3

Controversially I am going to say for System.Net.Http.HttpClient you probably can just use .Result

Microsoft should have followed their own advice and used .ConfigureAwait(false) all the way down that library. The reference source implies this:

https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs

Why take the risk? Because it should be slightly more lightweight than Task.Run(() => Client.PostAsync()).Result.

I wouldn't trust many (any?) other libraries to be coded safely and look forward to seeing if anyone argues against my answer or better yet proves me wrong.

Share:
18,830
pm100
Author by

pm100

I was in SW from 1974 till 2019. Retired as CTO of SV based SW company that I cofounded. Started with cobol on punched cards. Ended with v large scale SAAS c# projects. And linux stuff in go. current fun project, writing PDP11 emulator in c++17. Cos why not -:) also writing a few bits in rust.

Updated on June 05, 2022

Comments

  • pm100
    pm100 almost 2 years

    I like the new System.Net.Http.HttpClient class. It has a nice simple API, it doesn't throw on normal errors. But its async only.

    I need code that goes (deep inside a server)

    foo();
    bar();
    // compute stuff
    var x = GetThingFromOtherServerViaHttp();
    // compute more stuff
    wiz(x);
    

    classic sequential synchronous code. I saw several SO question that were similar but never actually ended up saying 'do this'. I looked at

    client.PostAsync.Wait()
    

    the world screams 'dont do this'. How about:

    client.PostAsync.Result()
    

    isnt this just Wait in disguise?

    In the end I ended up passing in a lambda callback that processed the result and then awoke the calling thread that was explicitly waiting on a EventWaitHandle. A lot of plumbing. Is there something a little simpler or shoul I just go back to using the old http clients

    EDIT:After further reading I suspect that this code has the same issues as Wait and Result, its just a more longwinded deadlock

    EDIT: I had MS PM confirm to me recently that there is a mandate 'any API that might take > X ms (I forgot X) must be async', many PMs interpret this as 'async only' (not clear if this is what is intended). Hence the Document DB api being only async.

  • pm100
    pm100 over 8 years
    good answer, but I still dont like the fact that the same code written in different places will deadlock. I would expect the wait call in a deadlock-prone environment to throw.
  • pm100
    pm100 over 8 years
    i have to say that I dont agree about 'async all the way'. Writing every server to look like node.js doesnt have a lot of advantages and has a lot of downside. I hope the .net team doesnt get into the habit of writing new APIs that are 'async only'
  • Aron
    Aron over 8 years
    @pm100 firstly, it is a technical problem. It is very hard to write the framework otherwise. But more importantly, I disagree with you that there are disadvantages to async all the way down. On the contrary you should learn about the scalability advantages of async all the way down. It's why a Java webserver can only serve ~1000 concurrent requests, whilst a single thread in node.js can serve millions with a fraction of the resources.
  • noseratio
    noseratio over 8 years
    @pm100, check my update. As to node.js, there it's common to struggle with callback pyramids of hell (but that problem is coming to to the end with ES6 generators and ES7 async/await, either). With C# async/await you naturally have pseudo-linear, pseudo-synchronous flow of non-blocking code, which can easily be converted to from existing synchronous code. E.g., with ASP.NET you simply make your controller methods async and replace synchronous blocking calls with their asynchronous equivalents + await. Even legacy WebForms have `RegisterAsyncTask, etc.
  • Matt Johnson-Pint
    Matt Johnson-Pint almost 8 years
    The article actually says don't do this unless you absolutely have to. I'm not sure the OP's usage qualifies under the narrow path of when you would want to do this that is described in the article.
  • Jeff Dunlop
    Jeff Dunlop over 7 years
    That's fine, leave him a comment to that effect, but "you don't want to do that" really isn't an answer. I did answer his question with the way that sucks least.
  • Dexter
    Dexter about 7 years
    Can this cause deadlocks? If it can't, I would gladly use it. The thing is, I've read that calling .Result(), .Wait() or other things on HttpClient directly could cause deadlocks.
  • Panagiotis Kanavos
    Panagiotis Kanavos about 7 years
    Don't do that! What's the point of using a task to run an asynchronous method only to block on it? Either use the method properbly with var result=await Client.PostAsync(); or block directly with Client.PostAsync().Result. If you fear that awaiting can block, eg because the UI is blocked by another operation, use ConfigureAwait(false), ie var result=await Client.PostAsync().ConfigureAwait(false);;
  • Thanasis Ioannidis
    Thanasis Ioannidis about 7 years
    @PanagiotisKanavos Task.Run(() => Client.PostAsync()) is pretty much what Client.PostAsync().ConfigureAwait(false) does. It ensures that the continuation of Client.PostAsync() will be scheduled in the ThreadPool and not in the context of the initial call. What Task.Run does is wrapping the async call of the Client inside an async call that will be executed in the ThreadPool. As a result, the continuation of Client async call will be scheduled to the ThreadPool context. ConfigureAwait was introduced pretty much to do the same think in a framework supported way.
  • pm100
    pm100 almost 7 years
    I note that the documentdb API is async only.
  • Don Cheadle
    Don Cheadle almost 7 years
    In some cases, having .Result inside of a .NET MVC controller for example can cause deadlock. Usually if something else is doing await below the .Result
  • Don Cheadle
    Don Cheadle almost 7 years
    @Noseratio - then why is it sometimes OK / no deadlock when accessing .Result in a Web Controller?
  • noseratio
    noseratio almost 7 years
    @mmcrae, in your controller, what's the value of System.Threading.SynchronizationContext.Current right before calling Client.PostAsync().Result ?
  • Don Cheadle
    Don Cheadle almost 7 years
    Not in my IDE/project right now. But I gather you're saying as long as there's not 2 different contexts, there's no problem?
  • noseratio
    noseratio almost 7 years
    @mmcrae, I'd rather expect there might be no context at all, like with ASP.NET Core.
  • Don Cheadle
    Don Cheadle almost 7 years
    @Noseratio - I guess I'm really wondering why .Result causes a dead lock in certain cases, when obviously SOMETHING must wait/call .Result for the result to do anything.
  • noseratio
    noseratio almost 7 years
    @mmcrae, I guess that's because the designers of this API don't want us to call task.Resut. They want us to use await task all the way through, including in a web controller, where controller's actions should be asynchronous.
  • KCD
    KCD almost 7 years
    Correct if you go from async to sync higher up the call chain and did not use .ConfigureAwait(true) all the way down to HttpClient. In other words your MyAsyncHttpThing was not safe from deadlocks and the simplest rule to follow is still "never use .Result"
  • KCD
    KCD almost 7 years
    Unless you mean mixing .Result on HttpClient in a async Controller.... which is very interesting I have not considered that case. But there is no good reason to do that either??
  • Don Cheadle
    Don Cheadle almost 7 years
    I think you mean .ConfigureAwait(false) btw. Passing false means that when it resumes, it should NOT attempt to resume on the context that it was started by (hence in a Controller, it should NOT try to resume on the UI context. That causes deadlocks)
  • Don Cheadle
    Don Cheadle almost 7 years
    blog.stephencleary.com/2012/07/dont-block-on-async-code.html this is an example of how .Result inside of a Controller can cause a deadlock (key is that only one thread can be on the UI context at a time, and .Result will wait inside the UI context which prevents the await'ed thing from getting back in the context to return)