await vs Task.Wait - Deadlock?
Solution 1
Wait
and await
- while similar conceptually - are actually completely different.
Wait
will synchronously block until the task completes. So the current thread is literally blocked waiting for the task to complete. As a general rule, you should use "async
all the way down"; that is, don't block on async
code. On my blog, I go into the details of how blocking in asynchronous code causes deadlock.
await
will asynchronously wait until the task completes. This means the current method is "paused" (its state is captured) and the method returns an incomplete task to its caller. Later, when the await
expression completes, the remainder of the method is scheduled as a continuation.
You also mentioned a "cooperative block", by which I assume you mean a task that you're Wait
ing on may execute on the waiting thread. There are situations where this can happen, but it's an optimization. There are many situations where it can't happen, like if the task is for another scheduler, or if it's already started or if it's a non-code task (such as in your code example: Wait
cannot execute the Delay
task inline because there's no code for it).
You may find my async
/ await
intro helpful.
Solution 2
Based on what I read from different sources:
An await
expression does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async
method as a continuation on the awaited task. Control then returns to the caller of the async
method. When the task completes, it invokes its continuation, and execution of the async
method resumes where it left off.
To wait for a single task
to complete, you can call its Task.Wait
method. A call to the Wait
method blocks the calling thread until the single class instance has completed execution. The parameterless Wait()
method is used to wait unconditionally until a task completes. The task simulates work by calling the Thread.Sleep
method to sleep for two seconds.
This article is also a good read.
Solution 3
Some important facts were not given in other answers:
"async await" is more complex at CIL level and thus costs memory and CPU time.
Any task can be canceled if the waiting time is unacceptable.
In the case "async await" we do not have a handler for such a task to cancel it or monitoring it.
Using Task is more flexible then "async await".
Any sync functionality can by wrapped by async.
public async Task<ActionResult> DoAsync(long id)
{
return await Task.Run(() => { return DoSync(id); } );
}
"async await" generate many problems. We do not now is await statement will be reached without runtime and context debugging. If first await not reached everything is blocked. Some times even await seems to be reached still everything is blocked:
https://github.com/dotnet/runtime/issues/36063
I do not see why I'm must live with the code duplication for sync and async method or using hacks.
Conclusion: Create Task manually and control them is much better. Handler to Task give more control. We can monitor Tasks and manage them:
https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem
Sorry for my english.
Related videos on Youtube
ronag
Master of Science (M.Sc.), Software Engineering and Technology at Chalmers University of Technology. I wrote most of CasparCG 2.0 Server, an open-source video- and graphics playout server used by the Swedish Broadcasting Corporation 24/4 for all regional and national broadcasts in Sweden. Big fan of the ffmpeg project.
Updated on August 21, 2022Comments
-
ronag over 1 year
I don't quite understand the difference between
Task.Wait
andawait
.I have something similar to the following functions in a ASP.NET WebAPI service:
public class TestController : ApiController { public static async Task<string> Foo() { await Task.Delay(1).ConfigureAwait(false); return ""; } public async static Task<string> Bar() { return await Foo(); } public async static Task<string> Ros() { return await Bar(); } // GET api/test public IEnumerable<string> Get() { Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray()); return new string[] { "value1", "value2" }; // This will never execute } }
Where
Get
will deadlock.What could cause this? Why doesn't this cause a problem when I use a blocking wait rather than
await Task.Delay
?-
ronag over 11 years@Servy: I will get back with a repo as soon as I have time. For now it works with
Task.Delay(1).Wait()
which is good enough. -
Servy over 11 years
Task.Delay(1).Wait()
is basically the exact same thing asThread.Sleep(1000)
. In actual production code it is rarely appropriate. -
Stephen Cleary over 11 years@ronag: Your
WaitAll
is causing the deadlock. See the link to my blog in my answer for more details. You should useawait Task.WhenAll
instead. -
Servy over 11 yearsYour code is deadlocking because you're blocking on the results of an asynchronous operation, just as the link in Stephen's answer shows. You need to
await
all the way up for it to work. Likewise, you can block all the way down and it will work. In your example where you block you actually never actuallyawait
, you block all the way, so it won't deadlock (it also blocks the thread during the asynchronous operation rather than leaving the thread free). -
ronag over 11 yearsI don't quite understand I am doing
ConfigureAwait(false)
as shown in the link you refer to. I cannot use async all the way up as that would require to much code changes in my actual code. -
Servy over 11 years@ronag Because you have
ConfigureAwait(false)
a single call toBar
orRos
won't deadlock, but because you have an enumerable that is creating more than one and then waiting on all of those, the first bar will deadlock the second. If youawait Task.WhenAll
instead of waiting on all of the tasks, so that you don't block the ASP context, you'll see the method return normally. -
ronag over 11 years@Servy: Ah! Now I understand. Thank you.
-
Servy over 11 years@ronag Your other option would be to add the
.ConfigureAwait(false)
all the way up the tree until you block, that way nothing is ever trying to get back to the main context; that would work. Another option would be to spin up an inner synchronization context. Link. If you put theTask.WhenAll
in anAsyncPump.Run
it will effectively block on the whole thing without you needing toConfigureAwait
anywhere, but that's probably an overly-complex solution. -
ruffin about 7 yearsPossible duplicate of What's the difference between Task.Start/Wait and Async/Await?
-
-
ronag over 11 yearsI think there is a missunderstanding,
Wait
works fineawait
deadlocks. -
ronag over 11 yearsI don't mean that the task I'm waiting on may execute on the waiting thread. What I mean is that while waiting the task scheduler will execute other tasks on the thread that called
Wait
. -
ronag over 11 yearsClearly: Yes, if I replace my
await Task.Delay(1)
withTask.Delay(1).Wait()
the service works fine, otherwise it deadlocks. -
Stephen Cleary over 11 yearsNo, the task scheduler won't do that.
Wait
blocks the thread, and it cannot be used for other things. -
ronag over 11 yearsClearly: Interesting, I wouldn't have thought that, and I'm a bit surprised. In PPL/Concrt any blocking operation is cooperative as I have described.
-
Stephen Cleary over 11 yearsI'm not able to get your
await
-based code to deadlock on my machine. Could you provide a repo? -
ronag over 11 yearsThere is quite a lot of code go through to create a repo. I will try to provide a repo, though that will have to wait a while until I have time. It works right now, which is good enough for now, though I am very curious about what the problem with
await
might be. -
Servy over 11 years@ronag My guess is you just got your method names mixed up and your deadlock was actually caused with the blocking code and worked with the
await
code. Either that, or the deadlock was unrelated to either and you mis-diagnosed the problem. -
Stack Undefined over 7 yearsDid the .NET team drop the ball on this deadlock issue when they let the context to be blocked and also allow the scheduling of the continuation on the same context or is this by design?
-
Stephen Cleary over 7 years@hexterminator: This is by design - it works great for UI apps, but does tend to get in the way for ASP.NET apps. ASP.NET Core has fixed this by removing the
SynchronizationContext
, so blocking within an ASP.NET Core request no longer deadlocks. -
AfshinZavvar almost 7 years@StephenCleary Can we overcome deadlocks by using await Task.WaitAll() instead of using Task.WaitAll() please?
-
Stephen Cleary almost 7 years@Afshin_Zavvar: Blocking on asynchronous code must either allow deadlocks or reentrancy. There is no clean solution.
-
Emil almost 7 yearson msdn here, it says that wait runs on separate thread asynchronously. Am I missing something? msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx
-
Stephen Cleary almost 7 years@batmaci: That's talking about
Task.Run
, notasync
/await
. -
Emil almost 7 years@StephenCleary so I wanted to know, like in msdn article. this code will run async in sync wrapper? var t = Task.Run(() => { Console.WriteLine("Task thread ID: {0}", Thread.CurrentThread.ManagedThreadId); } ); t.Wait();
-
Stephen Cleary almost 7 years@batmaci: If by "this code", you mean the code in this question and answer, then no, it is not async-over-sync. If by "this code", you mean the code posted in your comment on this answer, then yes, it runs async-over-sync.
-
Joe Phillips over 6 yearsSo how do we get around this if we can't use an async entry point .. which seems to be pretty common?
-
Stephen Cleary over 6 years@JoePhillips: See the hacks in my brownfield async article.
-
Marc Gravell over 5 years"Isn't that technically incorrect then? Can someone please clarify?" - can I clarify; are you asking that as a question? (I just want to be clear whether you're asking vs answering). If you're asking: it may work better as a separate question; it is unlikely to gather new responses here as an answer
-
Ayushmati over 5 yearsI have answered the question and asked a separate question for the doubt I had here stackoverflow.com/questions/53654006/… Thanks @MarcGravell. Can you please remove your deletion vote for the answer now?
-
Marc Gravell over 5 years"Can you please remove your deletion vote for the answer now?" - that isn't mine; thanks to the ♦, any such vote by me would have taken effect immediately. I don't, however, think that this answers the key points of the question, which is about the deadlock behaviour.
-
user1785960 about 4 years@Joe Phillips: Another approach is wrap sync implementation: public async Task<ActionResult> DoAsync(long id) { return await Task.Run(() => { return DoSync(id); } ); } I do not see why I'm must live with the code duplication.
-
Stephen Cleary about 4 years@user1785960: The thread pool hack does not work in all cases. Check out the brownfield async article for an approach that is not a hack and does not cause code duplication.
-
adamasan about 4 years@StephenCleary That's so trippy. I'm here because I'm having trouble understanding a bit of the Concurrency in C# Cookbook where you you first mention deadlocks, and after some time I realize that you're the author.
-
Stephen Cleary about 4 years@artie: If you have any recommendations on how to make the book better, I'd love to hear them!
-
user1785960 almost 4 yearsThis is not true. Until first await not reached everything is blocked
-
user1785960 almost 4 years@Stephen Cleary: This is not true: " Wait blocks the thread, and it cannot be used for other things." public bool Wait(int millisecondsTimeout); So It can be used for other tings and block only for some timeout. We can tell client: the task take to long time and you should ask about product leter.
-
Stephen Cleary almost 4 years@user1785960: "
Wait
blocks the thread for the duration of the call, and while that blocking is occurring, the thread cannot be used for other things." - How's that? -
Soner from The Ottoman Empire over 3 years@user1785960 though you are right, it doesn't mean the fact the answer is useless.