WinForms消息循环没有响应

本文关键字:响应 循环 消息 WinForms | 更新日期: 2023-09-27 18:03:01

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

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

有什么建议吗?

WinForms消息循环没有响应

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

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

输入消息留在消息队列中,直到代码停止添加更多调用请求,并且在代码停止递归之后,正常的消息循环泵送恢复。在此期间,您的UI将看起来冻结,因为Paint事件也不会交付。它们只在消息队列为空时生成。

重要的是它的工作方式,PostMessage()调用不能保证工作。Windows不允许消息队列中的消息超过10,000条。但是Control.BeginInvoke()没有这样的限制。通过完全清空调用队列,丢失的PostMessage消息不会引起任何问题。不过,这种行为确实会导致其他问题。一个典型的例子是过于频繁地调用BackgroundWorker.ReportProgress()。同样的行为,UI线程只是被调用请求淹没,不再绕过它的正常职责。如果有人遇到这样的情况:"我正在使用BackgroundWorker,但是我的UI 仍然死机了"。 不管怎样,你的实验是个彻底的失败。需要调用Application.DoEvents()来强制清空消息队列。关于这个问题有很多注意事项,请查看这个答案了解细节。即将到来的对async关键字的支持将提供另一种方法来实现这一点。不确定它是否以不同的方式对待消息优先级。我相当怀疑它,Control.BeginInvoke()是相当核心的。解决这个问题的一种方法是使用间隔非常短的Timer。定时器消息也会进入消息队列(某种程度上),但它们的优先级非常低。首先处理输入事件。或者是一个低级的hack:用你自己的消息调用PostMessage并覆盖WndProc来检测它。这就有点偏离正道了。应用程序。空闲事件用于在检索任何输入事件后进行处理。

使用具有优先级的begininvoke重载。"正常"优先级高于输入和渲染。您需要选择applicationidle