在新任务中创建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);
        }

在新任务中创建WinForm UI控件后等待会导致死锁

首先,您应该在不希望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元素。