【问题标题】:Invoke() is blockingInvoke() 被阻塞
【发布时间】:2008-11-13 14:55:06
【问题描述】:

我的应用程序 GUI 有时会停止重绘。 有很多线程正在触发各种事件(如计时器或网络数据就绪等)。还有很多订阅这些事件的控件。因此,所有事件处理程序都玩 InvokeRequired/Invoke 游戏。 现在我发现当 GUI 冻结时,很多线程都在等待 Invoke() 返回。看起来消息泵停止了泵送。 处理程序如下所示:

private void MyEventHandler( object sender, EventArgs e ) {
    if ( InvokeRequired ) {
        Invoke( new EventHandler( MyEventHandler ), sender, e );
        return;
    }

    SetSomeStateVariable();
    Invalidate();
}

有什么想法吗?

解决方案:BeginInvoke()。如果您有很多 CrossThread-Events,您应该始终使用 BeginInvoke()...

谢谢。

谢谢大家。

编辑: 看起来BeginInvoke() 真的解决了它。直到现在都没有冻结。

【问题讨论】:

  • 当它冻结时谁在编组?它是一个特定的控件或事件,还是随机的?
  • 我不太明白你的意思。
  • 您的事件一次一个地编组到 UI 线程。在此过程中有人阻塞。我在问每次阻塞时是否编组相同的方法,和/或每次阻塞时更新的控件是否相同。
  • 我不知道,因为我只在调试器中捕获过一次。在这种情况下,“不时”的意思是“可能在 6 个月内十次,并且只有一次在带有调试器的盒子上”。

标签: c# .net winforms .net-2.0


【解决方案1】:

Invoke 一直等到事件在 GUI 线程中得到处理。如果您希望它是异步的,请使用 BeginInvoke()

【讨论】:

  • 我知道。 BeginInvoke 可能只是隐藏问题。所有 Invokes 都会无限期阻塞。
  • EricSchaefer,你没抓住重点。 Invoke 一直等到您调用的事件完成。但是,只有在当前函数执行完毕后才会调用调用。你陷入僵局。 BeginInvoke 不会等待被调用函数执行,因此不会出现死锁。
  • 也许我错过的不止一点。调用 MyEventHandler 的事件由另一个线程(例如,等待来自网络的数据的线程)触发,而不是 UI 事件。此外,它只会“不时”冻结。
  • 您的问题是大约同时触发的两个线程相互阻塞 - 调用调用都无法完成。 BeginInvoke 这样应该是安全的。
  • 哦,好的。现在我懂了。我会试试的。但是请不要屏住呼吸,因为这个问题不容易重现。谢谢。
【解决方案2】:

也许是死锁?您是否确保在持有锁时永远不会触发事件?

你能在附加调试器的情况下看到这个吗?如果是这样,让它冻结,然后点击“暂停”按钮 - 看看 UI 线程在做什么。

请注意,如果您能够使用 BeginInvoke 而不是 Invoke,生活会轻松一些,因为它不会阻塞。

另请注意,您不需要“new EventHandler”位 - 只需

Invoke((EventHandler) MyEventHandler, sender, e);

应该没问题。

【讨论】:

  • 仅凭记忆:我认为您应该在适当的控件上调用 Invoke()。然后控件应该在 gui 线程中执行该方法。如果我错了,请告诉我。
  • @EricShaefer:如果您为每个线程打开堆栈,应该很明显哪个是 UI 线程:)
【解决方案3】:

通过观看这个问题,我可以看出您不会得到任何可以立即解决问题的答案,因为其中大多数都需要您调试事件,而且这种情况很少发生,这几乎是不可能的。因此,我建议您进行一些代码更改,以帮助您识别该领域的罪魁祸首。

我建议您创建一个静态类,其唯一目的是处理您的所有 Invoke 调用。我建议这个类有一个方法,它接受一个 Control,(调用 Invoke)一个 Action(要调用的方法)和一个描述(包含你需要知道的信息来识别方法和它是什么会做)。

在这个方法的主体内,我建议你把这个信息(方法、描述)排入队列并立即返回。

队列应该由单个线程提供服务,该线程将动作/消息对从队列中弹出,在一对属性中记录当前时间和动作的描述,然后调用()动作。当 Action 返回时,描述和时间被清除(您的 DateTime 可以为空,或将其设置为 DateTime.Max)。请注意,由于一次将所有 Invokes 编组到 UI 线程上,因此在这里通过单个线程为队列提供服务不会丢失任何内容。

现在,这就是我们要讨论的重点。我们的 Invoking 类应该有一个心跳 System.Threading.Timer 线程。这不应该是 windows.forms.timer 对象,因为它在 UI 线程上运行(并且会在 ui 被阻止时被阻止!!!)。

此计时器的工作是定期查看当前操作被调用的时间。如果 DateTime.Now - BeginTime > X,心跳计时器将判定此 Action 已被阻止。心跳计时器将记录(无论您如何记录)为该操作记录的说明。您现在可以记录您的 UI 锁定时发生的情况,并且可以更好地对其进行调试。

我知道这不能解决你的问题,但至少通过这样做你可以很好地了解你被阻止时发生的事情。

【讨论】:

  • 好主意。无论如何,我将不得不触摸所有 55 个(!)调用。
  • 当我按照您的建议进行操作时,没有发生冻结。所以我把所有的 Invoke()s 都改成了 BeginInvoke()s。
【解决方案4】:

已经提出了最可能的答案(死锁)。

模拟这种行为的另一种方法是减少池线程和IO完成端口的数量;你还没有打电话给ThreadPool.SetMaxThreads(...)

【讨论】:

  • 没有。 (cmets 至少需要 10 个字符...)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多