Task.WaitAll keeps in loop

I'm trying this async code just for testing the async keyword:

public async Task<string> AsyncMethod()
{
    var link = "http://www.google.com";

    var webclient = new WebClient();
    var result = await webclient.DownloadStringTaskAsync(new Uri(link));

    return result;
}

public async Task<ActionResult> Index()
{
    var a = AsyncMethod();
    var b = AsyncMethod();

    Task.WaitAll(a, b);

    return View();
}

but when I debug it, the debugger hits the Task.WaitAll and just do nothing(the return keywork is never executed).. If I set await before the two 'AsyncMethod' and remove the Task.WaitAll it works.. So what am I doing wrong?


Solution 1:

Because your method looks like ASP.NET MVC controller action, I'm assuming you're running on ASP.NET.

By default, an async method resumes on the same context where it was suspended (i.e. where you called await). In ASP.NET, this means the current request context. And only one thread can be in a specific context at a time. So, what happens is that the thread that executes Index() is in the request context, blocked in WaitAll(). On the other hand, both invocations of AsyncMethod() are trying to resume on the same context (after they finished downloading), but they are unable to do so, because Index() is still executing in that context. Because of this, the methods are in a deadlock and so nothing happens.

(The same deadlock would also happen in an GUI application, because GUI context behave similarly in this regard. Console applications don't have this issue, because they don't have any context.)

The fix for this is twofold:

  1. Never wait synchronously for an async method. (Probably the sole exception is if want to execute an async method from the Main() method of a console application.)

    Instead, wait for them asynchronously. In your case, that means using await Task.WhenAll(a, b).

  2. Use ConfigureAwait(false) in your "library" methods (i.e. those that don't actully need to execute on the request context).

Using 1 or 2 would fix your issue, but it's probably best if you do both.

For some more information about this issue, read Stephen Cleary's article Don't Block on Async Code.