【问题标题】:Await after creating WinForm UI Control within a new Task causes deadlock?在新任务中创建 WinForm UI 控件后等待导致死锁?
【发布时间】:2016-02-10 12:26:46
【问题描述】:

我在 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);
        }

【问题讨论】:

  • 您应该只在 UI 线程上创建 UI 控件。你也不应该是async void,而是使用async Task
  • 我在更新的代码中使用异步任务,我知道它是一个更好的模式。然而问题依然存在。我认为这与 Task.Delay 试图在等待它的同一线程中恢复有关。

标签: c# winforms async-await


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 2014-06-15
    相关资源
    最近更新 更多