【问题标题】:Deadlock using Control.Invoke?使用 Control.Invoke 死锁?
【发布时间】:2015-07-28 01:40:46
【问题描述】:

我正在 VS2010 Ultimate 中使用 TPL 构建应用程序。大多数情况下,当我从 UI 线程调用 DoRepresentation() 时,我运行该应用程序时它会变得无响应。

void DoRepresentation()
{
  Parallel.ForEach(cgs, loopOptions, g =>
  {
    UpdateRepresentation(g);
  });
}

void UpdateRepresentation(object g)
{
  view.Invoke(new Action(() =>
  {
    representation = new MyRepresentation(g);
  }));
}

我不知道为什么应用程序变得无响应。我遇到了僵局吗?

MyRepresentation 内部我对 OpenGL 进行了一些调用。

view 是 Form1(主窗体)内的一个 Control。

当应用程序变得无响应时,我从 VS IDE 暂停它,这是我得到的信息

在“并行任务”窗口中,我得到以下信息:

ID  Status       Message<br>
1    ?Waiting   Task1 is waiting on object: "Task2"<br>
2    ?Waiting   No waiting information available<br>

在“调用堆栈”窗口中,我得到以下信息:

[In a Sleep, wait, or join]<br>
[External Code]<br>
Test.dll!Render.DoRepresentation()<br>
App1.exe!Form1.Button1_Click<br>

我们将不胜感激。

【问题讨论】:

  • 你不能用BeginInvoke而不是Invoke吗?
  • 否,因为我需要阻止直到 UpdateRepresentation() 返回。因为在 Parallel.ForEach() 之后,我对某些方法进行了其他调用。
  • 也许是个愚蠢的问题,但是 View 是否有 Dispatcher 以便您可以执行 view.Dispatcher.Invoke()?
  • MyRepresentation的构造函数需要在UI线程上调用吗?
  • 一个类似的问题(和可能的解决方案)已经发布在这里:stackoverflow.com/questions/3467816/…

标签: c# winforms task-parallel-library


【解决方案1】:

是的,您遇到了死锁。 Parallel.ForEach() 所做的是它使用一个或多个线程运行迭代包括当前线程,然后阻塞当前线程直到所有迭代都完成。

这意味着如果你从 UI 线程调用 DoRepresentation(),你会遇到死锁:UI 线程正在等待其他线程上的迭代完成,而其他线程正在等待 Invoke() 完成,这可以'如果 UI 线程被阻塞,则不会发生。

此外,在您的情况下,使用 Parallel.ForEach() 没有任何意义(假设这是您的实际代码):您在 UI 线程上运行 new MyRepresentation()

我不明白代码到底在做什么(似乎它在每次迭代中都会覆盖representation),但我认为您应该从后台线程运行ForEach()。这意味着DoRepresentation() 将在完成工作之前返回,因此Invoke() 将正常工作。

一般来说,长时间阻塞UI线程并不是一个好主意,所以你应该在另一个线程上运行任何耗时的代码。

【讨论】:

  • 当您在第 1 段中说“包括当前的”时,您是在谈论 UI(主)线程吗?我必须在 UI 线程上执行 MyRepresentation(),因为我正在对 OpenGL 的 API 进行一些调用,并且上下文是在 UI 线程中创建的
  • 是的,如果您从 UI 线程运行 ForEach(),那么它的一些迭代将在 UI 线程上运行。如果你必须在 UI 线程上运行new MyRepresentation(),那你为什么还要尝试使用Parallel.ForEach()?代码中似乎没有任何东西可以被并行化。
  • @sivck 感谢您的帮助,现在我已经 100% 清楚发生了什么。您对使用 Parallel.ForEach() 执行 MyRepresentation() 是正确的,但我必须在 MyRepresentation 内部进行一些密集计算,并且在 Invoke 内部进行很少的行来调用 OpenGL 的 API。但是为了简单起见,我没有在这里展示它
【解决方案2】:

您可以使用 BeginInvoke 代替 Invoke 方法。如果您仍然需要,那么您可以锁定一个对象,并确保在实现之前无法从其他线程访问它。

使用 Begin Invoke 方法

void UpdateRepresentation(object g)
{
  view.BeginInvoke( new Action(() =>
  {
    representation = new MyRepresentation(g);
  }));
}

使用锁

void UpdateRepresentation(object g)
{
lock(this) 
{
 view.Invoke(new Action(() =>
  {
    representation = new MyRepresentation(g);
  }));
}

}

【讨论】:

  • 那么 Control.Invoke() 会造成死锁吗?
  • 是的,如果您正在调用的控件正在等待调用 Control.Invoke 的线程完成,Control.Invoke 可以创建死锁。 Control.Invoke 在某些情况下会造成死锁 - 解决这个问题是你的任务:-)
  • @ThorstenDittmar 谢谢你的解释。但事实并非如此,因为视图控件是第三方的
  • @Jodha 我已经尝试过像你建议的那样使用锁,但我仍然处于同样的情况
  • lock 在这里无济于事。你仍然会遇到同样的死锁。
【解决方案3】:

此评论适用于我的特定应用程序,它是 C# 中的 Windows 应用程序:使用锁对我也不起作用,应用程序只是冻结了。 BeginInvoke 有效,但我不喜欢 UI 控件异步更新的效果。

我最终将主进程作为一个单独的线程 (System.Threading.Tasks.Task) 启动,它会启动并立即让我重新控制主线程。之后,在循环等待其他几个任务结束执行时,我还不得不插入这一行:System.Windows.Forms.Application.DoEvents() 以使系统能够处理队列中等待的所有消息。现在它适用于我的应用程序。可能有另一种方法可以给这只猫剥皮,但现在可以了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-03
    相关资源
    最近更新 更多