Reputation: 926
I was writing a small console app to try to become familiar with using async/await. In this app, I accidentally created an infinite recursive loop (which I have now fixed). The behavior of this infinitely recursive loop surprised me though. Rather than throwing a StackOverflowException
, it became deadlocked.
Consider the following example. If Foo()
is called with runAsync
set to false
, it throws a StackOverflowException
. But when runAsync
is true
, it becomes deadlocked (or at least appears to). Can anyone explain why the behavior is so different?
bool runAsync;
void Foo()
{
Task.WaitAll(Bar(),Bar());
}
async Task Bar()
{
if (runAsync)
await Task.Run(Foo).ConfigureAwait(false);
else
Foo();
}
Upvotes: 2
Views: 563
Reputation: 116548
The async version doesn't deadlock (as usr explained) but it doesn't throw a StackOverflowException
because it doesn't rely on the stack.
The stack is a memory area reserved for a thread (unlike the heap which is shared among all the threads).
When you call an async method it runs synchronously (i.e. using the same thread and stack) until it reaches an await on an uncompleted task. At that point the rest of the method is scheduled as a continuation and the thread is released (together with its stack).
So when you use Task.Run
you are offloading Foo
to another ThreadPool
thread with a clean stack, so you'll never get a StackOverflowException
.
You may however, reach an OutOfMemoryException
because the async method's state-machine is stored in the heap, available for all threads to resume on. This example will throw very quickly because you don't exhaust the ThreadPool
:
static void Main()
{
Foo().Wait();
}
static async Task Foo()
{
await Task.Yield();
await Foo();
}
Upvotes: 1
Reputation: 171178
It's not really deadlocked. This quickly exhausts the available threads in the thread-pool. Then, one new thread is injected every 500ms. You can observe that when you put some Console.WriteLine
logging in there.
Basically, this code is invalid because it overwhelms the thread-pool. Nothing in this spirit may be put into production.
If you make all waiting async instead of using Task.WaitAll
you turn the apparent deadlock into a runaway memory leak instead. This might be an interesting experiment for you.
Upvotes: 6