Search code examples
c#async-awaittaskcompletionsource

Awaiting a Task never completes even though its state transitions to 'RanToCompletion'


First, apologies -- I'm unable to reproduce this behaviour in a suitably simple sample application. This functionality was working before some refactoring of the calling code.

I'm trying to use a TaskCompletionSource to signal the end of an async operation (either a long-running process completing, or a timeout may signal the completion using TrySetResult() ).

My issue is that even though I can see Task is transitioning from "WaitingForActivation" to "RanToCompletion", the call to await never completes.

As a test, I created a Task Continuation, and this IS being called, and I added a Timer to show the Task states:

async Task<Foo> WaitForResultOrTimeoutAsync()
{
        //... [Create 'pendingReq' with its TaskCompletion property]

        TaskCompletionSource<Foo> myCompletion = pendingReq.TaskCompletion;

        Task<Foo> theTask = myCompletion.Task;

        var taskContinuation = theTask.ContinueWith(resp =>
        {
            Console.WriteLine("The task completed");
            return resp.Result;
        });

        new Timer(state =>
        {
            Console.WriteLine("theTask TaskCompletion state is {0}", theTask.Status);
            Console.WriteLine("taskContinuation TaskCompletion state is {0}", taskContinuation.Status);
        }, null, 0, 1000);

        //var result = await theTask;
        var result = await taskContinuation;
        Console.WriteLine("We're FINISHED");    // NEVER GETS HERE
        return result;
}

This results in the following output:

theTask TaskCompletion state is WaitingForActivation
taskContinuation TaskCompletion state is WaitingForActivation
theTask TaskCompletion state is WaitingForActivation
taskContinuation TaskCompletion state is WaitingForActivation
The task completed
theTask TaskCompletion state is RanToCompletion
taskContinuation TaskCompletion state is RanToCompletion
theTask TaskCompletion state is RanToCompletion
taskContinuation TaskCompletion state is RanToCompletion

Surely with the continuation being hit, directly awaiting the Task should also complete, no? What external (calling) factors could there be for this behaviour?


Solution

  • I'm pretty sure that the calling code at some point further up the callstack is blocking on a task, and that this code is executed within a synchronization context (i.e., on a UI thread or from an ASP.NET request). This will cause a deadlock that I explain in full on my blog. The most correct solution is to replace the blocking (usually a Wait or Result call) with await.

    The reason ContinueWith is not blocked is because it uses the current TaskScheduler instead of the current SynchronizationContext, so it probably ends up running on the thread pool in this case. If my guess about the calling code blocking is correct, then ContinueWith would also deadlock if you pass it a TaskScheduler.FromCurrentSynchronizationContext().