Saturday, April 18, 2015

Why does my .NET app lock up with tasks?!

Such a silly mistake!  I'm pretty sure I even did this before in the past.  I was getting so frustrated that the async/await pattern was completely letting me down.  Tasks were never finishing, or just not consistently at all.

I'm using a cool pattern where you spin up lots of work (iterating a collection and creating tasks per item), then wait on all tasks at once.  It's so great because if some of the early tasks end before you start waiting on them, it makes no difference.  Code won't continue until all are done and you don't need to specially handle the ones that finish early.
var tasks = new List<Task>();
foreach( var item in iter )
{
    var tsk = DoThingAsync( item );
    tasks.Add( tsk );
}
Task.WaitAll( tasks.ToArray() );
Notice that you don't await the DoThingAsync() call.  This is intentional.  If you await here, then you won't move onto the next item in the collection until it completes.  Instead, you add it to the collection and use it as a group.  The problem is the method that I was using.  WaitAll() is the wrong method!  So deceptive.  None of my code would reliably complete and I couldn't figure out why.

The problem is that if you use WaitAll, the thread blocks until all tasks finish.  This could be fine in some cases, but if your tasks require the synchronization context (such as the HttpClient), they will lock up.  The simple change is just to the last line:
await Task.WhenAll( tasks.ToArray() );
Now, perfection!  The await completes when all tasks complete, and nothing is blocked.  Such beautiful simplicity, such a frustrating mistake!