c# - WinForms 消息循环没有响应

标签 c# winforms multithreading

我故意滥用 Windows 窗体应用程序中的消息循环,但我的“只是为了好玩”的项目很快就超出了我的理解水平。任务运行时,表单没有响应。是的,还有很多其他类似的问题,但就我而言,我故意避免在另一个线程上工作(为了赢得对我自己的赌注?)

我有一个在 UI 线程上运行(许多)短时间片的函数:get_IsComplete() 检查任务是否完成; DoWork() 从 0 到 1000 循环(只是为了让 CPU 保持温暖)。该任务通过调用 control.BeginInvoke(new Action(ContinueWith), control); 启动,然后它(尾递归地)调用自身直到完成,始终在 UI 线程上运行一小段工作。

public void ContinueWith(Control control)
{
    if (!IsComplete)
    {
        DoWork();
        OnNext(control);
        control.BeginInvoke(new Action(ContinueWith), control);
    }
    else
    {
        OnCompleted(control);
    }
}

我希望应用程序能够处理其他事件(鼠标点击、控件重绘、表单移动等),但我的调用似乎获得了比我想要的更高的优先级。

有什么建议吗?

最佳答案

control.BeginInvoke() 调用将您传递的委托(delegate)置于内部队列中,并调用 PostMessage() 唤醒消息循环并注意。这就是第一个 BeginInvoke 运行的原因。任何输入事件(鼠标和键盘)也会进入消息队列,Windows 将它们放在那里。

您没有预料到的行为存在于检索发布的消息时运行的代码中。它不只是将 一个 调用请求出列并执行它,它会循环直到整个调用队列被清空。您的代码的工作方式是,该队列永远不会清空,因为调用 ContinueWith() 会添加另一个调用请求。所以它只是不断循环和处理调用请求,而永远不会从消息队列中检索更多消息。或者换句话说:它正在抽取调用队列,而不是消息队列。

输入消息保留在消息队列中,直到您的代码停止添加更多调用请求并且在您的代码停止递归后常规消息循环泵恢复。发生这种情况时,您的 UI 看起来会卡住,因为 Paint 事件也不会传递。它们仅在消息队列为空时生成。

重要的是它按照它的方式工作,PostMessage() 调用不能保证工作。 Windows 不允许消息队列中的消息超过 10,000 条。但是 Control.BeginInvoke() 没有这样的限制。通过完全清空调用队列,丢失的 PostMessage 消息不会造成任何问题。但是,此行为确实会导致其他问题。一个典型的例子是过于频繁地调用 BackgroundWorker.ReportProgress()。同样的行为,UI 线程只是充斥着调用请求并且不再回避其正常职责。对遇到此问题的任何人皱眉:“我正在使用 BackgroundWorker,但我的 UI 仍然 卡住”。

不管怎样,你的实验是一次彻底的失败。需要调用 Application.DoEvents() 来强制清空消息队列。有很多注意事项,请查看 this answer了解详情。即将推出的对 async 关键字的支持将提供另一种方式来实现这一点。不太确定它是否以不同方式对待消息优先级。我很怀疑,Control.BeginInvoke() 是非常核心的。解决该问题的一种方法是使用间隔非常短的计时器。计时器消息也进入消息队列(某种程度上),但它们的优先级非常低。输入事件首先得到处理。或者一个低级的 hack:自己用自己的消息调用 PostMessage 并覆盖 WndProc 来检测它。这有点偏离直线和狭窄。 Application.Idle 事件对于在检索到任何输入事件后进行处理很有用。

关于c# - WinForms 消息循环没有响应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6789105/

相关文章:

C# 继承 : change field data type and value in derived class

c# - 传递对象的最佳实践

.net - AsParallel()和内部缓冲区大小

java - 简单的守护线程不输出消息

c# - Javascript 显示 50 个文本框值的总和

c# - 抽象属性的 MVC 属性

.net - Windows 窗体在左侧辅助监视器上无法调整大小

c# - 在 Winform 的 Devexpress 网格中选中取消选中复选框列

C#循环遍历颜色?

c++ - Qt线程设计生产者消费者