在新任务中创建WinForm UI控件后等待会导致死锁
本文关键字:等待 死锁 控件 新任务 创建 WinForm UI | 更新日期: 2023-09-27 18:21:18
我在WinForms应用程序中有这种奇怪的行为。我从主窗体的Form_Shown事件处理程序调用此任务。
Task.Run(() => WatchHistory(CancellationTokenSource.Token));
函数定义如下。FinishedRequestItem对象是一个WinForms UI控件。它在等待任务延迟时死锁。我注意到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);
}
首先,您应该在不希望await
在当前同步上下文中恢复的每个位置使用Task.ConfigureAwait(false)
。如果你在Task.Delay
电话中这样做,你就不会有这个问题(但很可能是另一个问题)。
但是,如果我使用Invoke方法创建UI控件,如下所示,它不会死锁。我很好奇为什么?由于任务不是在UI上下文中运行的,是什么阻止了task.Delay在上面的代码中恢复?
事实上,按照您使用它的方式,当您调用Task.Delay
时,它将尝试在当前同步上下文的任何位置恢复。通常情况下,线程池线程没有同步上下文(或者使用默认上下文,该上下文不进行任何同步)。但默认情况下,每个System.Windows.Forms.Control
都会在其构造函数中安装(将SynchronizationContext.Current
设置为)WindowsFormsSynchronizationContext。因此,在呼叫之后
Program.Container.GetInstance<FinishedRequestItem>()
当前同步上下文被更改,然后Task.Delay
尝试在该上下文上恢复,但当然不能,因为该线程上没有运行消息泵(这是WindowsFormsSynchronizationContext
正常工作所必需的)。
通常,您不应该在非UI线程上创建UI元素。