Search code examples
c#async-awaittaskthreadpool

Why it is not possible to join a task?


I recently come to a deadlock issue similar at the one described here: An async/await example that causes a deadlock

This question is not a duplicate of the one below.

Because I have access to the async part of the code I have been able to use the solution described by @stephen-cleary in his blog post here: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

So I added ConfigureAwait(false) on the first awaited Task.

I can't use the second solution (put async everywhere) because of the amount of impacted code.

But I keep asking myself, what if I can't modify the called async code (ex: extern API).

So I came to a solution to avoid the dead lock. A way to Join the task. By Join I mean a way to wait for the targeted task to end without blocking other tasks that's run on the current thread.

Here is the code:

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Call back the dispatcher to allow other Tasks on current thread to run
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

I want to emphasize that I'm not sure that the correct name for this method is Join.

My question is why Task.Wait() is not implemented this way or may be optionally used this way ?


Solution

  • This Join methods is simply busy waiting for the task to complete with an added equivalent of Application.DoEvents(). By repeatedly calling into the dispatcher like that you have essentially implemented a nested message pump that keeps the UI alive.

    This is a really bad idea because it drives the CPU to 100%.

    You really need to treat the UI message loop correctly and get off the UI thread when waiting. await is great for that.

    You say that you really want to avoid making all code async aware because it would be a lot of work. Maybe you can smartly use the await Task.Run(() => OldSynchronousCode()); pattern to avoid a lot of the work to do that. Since this runs on the UI thread the frequency of such calls should be very low. This means that the overhead caused by this is also very low and it's not an issue.