我在WinForms应用程序中有这种奇怪的行为。我从我的主窗体的Form_Shown事件处理程序中调用此任务。
Task.Run(() => WatchHistory(CancellationTokenSource.Token));
功能定义如下。 FinishedRequestItem对象是一个WinForms UI控件。它死了等待Task.Delay。我注意到,一旦创建了FinishedRequestItem对象,就会创建一个新的同步上下文。这个上下文与主窗体UI的上下文不同。
private async void WatchHistory(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var completedRequests = await processContext.ProcessRequests.ToListAsync(cancellationToken);
if (completedRequests.Any())
{
FinishedRequestItem entry = Program.Container.GetInstance<FinishedRequestItem>();
}
await Task.Delay(CurrentHistoryRefreshMilliseconds, cancellationToken);
}
}
但是,如果我使用Invoke方法创建UI控件,如下所示,它不会死锁。我很好奇为什么?由于任务没有在UI上下文中运行,是什么阻止Task.Delay在上面的代码中恢复?
while (!cancellationToken.IsCancellationRequested)
{
var completedRequests = await processContext.ProcessRequests.ToListAsync(cancellationToken);
if (completedRequests.Any())
{
FinishedRequestItem entry = null;
this.Invoke((Action)delegate()
{
entry = Program.Container.GetInstance<FinishedRequestItem>();
});
}
await Task.Delay(CurrentHistoryRefreshMilliseconds, cancellationToken);
}
答案 0 :(得分:2)
首先,您应该在每个不希望Task.ConfigureAwait(false)
在当前同步上下文中恢复的地方使用await
。如果您为Task.Delay
电话这样做,则不会出现此问题(但很可能是另一个问题)。
但是,如果我使用Invoke方法创建UI控件,如下所示,它不会死锁。我很好奇为什么?由于任务没有在UI上下文中运行,是什么阻止Task.Delay在上面的代码中恢复?
实际上,您使用它的方式,Task.Delay
将尝试在您调用它时的当前同步上下文中恢复。这就是重点。通常,线程池线程没有同步上下文(或使用有效不执行任何同步的默认上下文)。但默认情况下,每个System.Windows.Forms.Control
都会在其构造函数中安装(设置SynchronizationContext.Current
)WindowsFormsSynchronizationContext。因此在通话后
Program.Container.GetInstance<FinishedRequestItem>()
当前同步上下文已更改,然后Task.Delay
尝试在该上下文中继续,但当然不能,因为该线程上没有运行消息泵(这是{{1}所必需的正常运作)。
通常,您不应在非UI线程上创建UI元素。