best way to use the nice .net 4.5 HttpClient synchronously
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.
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, 2022Comments
-
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 over 8 yearsgood 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 over 8 yearsi 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 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 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 almost 8 yearsThe 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 over 7 yearsThat'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 about 7 yearsCan 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 about 7 yearsDon'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 withClient.PostAsync().Result
. If you fear that awaiting can block, eg because the UI is blocked by another operation, useConfigureAwait(false)
, ievar result=await Client.PostAsync().ConfigureAwait(false);
; -
Thanasis Ioannidis about 7 years@PanagiotisKanavos
Task.Run(() => Client.PostAsync())
is pretty much whatClient.PostAsync().ConfigureAwait(false)
does. It ensures that the continuation ofClient.PostAsync()
will be scheduled in the ThreadPool and not in the context of the initial call. WhatTask.Run
does is wrapping the async call of theClient
inside an async call that will be executed in the ThreadPool. As a result, the continuation ofClient
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 almost 7 yearsI note that the documentdb API is async only.
-
Don Cheadle almost 7 yearsIn some cases, having
.Result
inside of a .NET MVC controller for example can cause deadlock. Usually if something else is doingawait
below the.Result
-
Don Cheadle almost 7 years@Noseratio - then why is it sometimes OK / no deadlock when accessing .Result in a Web Controller?
-
noseratio almost 7 years@mmcrae, in your controller, what's the value of
System.Threading.SynchronizationContext.Current
right before callingClient.PostAsync().Result
? -
Don Cheadle almost 7 yearsNot 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 almost 7 years@mmcrae, I'd rather expect there might be no context at all, like with ASP.NET Core.
-
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 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 useawait task
all the way through, including in a web controller, where controller's actions should be asynchronous. -
KCD almost 7 yearsCorrect if you go from async to sync higher up the call chain and did not use
.ConfigureAwait(true)
all the way down toHttpClient
. In other words yourMyAsyncHttpThing
was not safe from deadlocks and the simplest rule to follow is still "never use.Result
" -
KCD almost 7 yearsUnless you mean mixing
.Result
onHttpClient
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 almost 7 yearsI 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 almost 7 yearsblog.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)