等待任务永远不会完成,即使其状态转换为“已运行完成”(RanToCompletion)。

4

首先,很抱歉--我无法在适当简单的示例应用程序中重现此行为。 在调用代码进行了一些重构之前,该功能是有效的。

我试图使用TaskCompletionSource来标志异步操作的结束(长时间运行的进程完成或超时可能会使用TrySetResult()来标志完成)。

我的问题是,即使我可以看到Task从“WaitingForActivation”转换为“RanToCompletion”,但等待的调用永远不会完成。

作为测试,我创建了一个任务连续体,并且这正在被调用。 我添加了一个计时器来显示任务状态:

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;
}

这将导致以下输出:
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
当持续运行时,直接等待任务也应该完成,是吗?有哪些外部(调用)因素可能导致这种行为?

4
请检查 SynchronizationContext.Current != null 是否成立。 - i3arnon
请你能否编辑你的代码,使得我们可以运行它以复制你的问题? - Enigmativity
谢谢 @i3arnon -- 这确实是 null,这让我找对了方向; - Ive
1
你的意思是不是指“非空”? - i3arnon
在等待任何东西之前,它被设置为WindowsFormsSynchronizationContext。我指的是在Continuation中,它为null。我怀疑这是由于Stephen关于Continuations使用TaskScheduler的注释...但还没有进一步调查。 - Ive
1个回答

4
我相信调用代码在调用栈中的某个更高层次上会阻塞一个任务,并且此代码在同步上下文中执行(即在 UI 线程或来自 ASP.NET 请求)。这将导致死锁,我在我的博客上对其进行了详细解释。最正确的解决方案是用await替换阻塞(通常是WaitResult调用)。 ContinueWith没有被阻塞的原因是它使用当前的TaskScheduler而不是当前的SynchronizationContext,所以在这种情况下它可能最终在线程池上运行。如果我的猜测关于调用代码阻塞是正确的,那么如果你传递了一个TaskScheduler.FromCurrentSynchronizationContext()ContinueWith也会死锁。

是的,你说得很对。在调用栈更高的位置,我正在处理从同步调用一些异步代码,并引用了一个Task.Result来实现这一点(我知道——有点不好)。这与使用WinForms应用程序的Page_Load事件测试此组件相结合,导致了你在博客中描述的死锁问题。随后我已经使用async一直回到原始的调用者,问题得到解决。在死锁的await上设置ConfigureAwait(false)也起到了作用。 - Ive

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接