WinForms消息循环无响应

Jon*_*ono 7 c# multithreading winforms

我故意滥用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

有什么建议?

Han*_*ant 17

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

您没有依赖的行为是在检索发布消息时运行的代码中.它不仅使一个调用请求出列并执行它,它将循环直到整个调用队列被清空.代码的工作方式,该队列永远不会被清空,因为调用ContinueWith()会添加另一个调用请求.因此它只是保持循环和处理调用请求,并且永远不会从消息队列中检索更多消息.或者换句话说:它是在调用队列,而不是消息队列.

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

重要的是它以它的方式工作,PostMessage()调用不能保证工作.Windows在消息队列中不允许超过10,000条消息.但是Control.BeginInvoke()没有这样的限制.通过完全清空调用队列,丢失的PostMessage消息不会导致任何问题.但是这种行为确实会导致其他问题.经典之一是经常调用BackgroundWorker.ReportProgress().同样的行为,UI线程只是充满了调用请求,并且不再绕过它的正常职责.对于遇到这种问题的人来说,我感到很沮丧:"我正在使用BackgroundWorker,但我的用户界面仍在冻结".

Anyhoo,你的实验是一个糟糕的失败.需要调用Application.DoEvents()来强制清空消息队列.有很多警告,请查看此答案以获取详细信息.即将支持的async关键字将提供另一种方法.不确定它是否以不同的方式处理消息优先级.我很怀疑它,Control.BeginInvoke()是非常核心的.一个问题就是使用具有非常短的间隔的Timer.定时器消息也会出现在消息队列中(排序)但它们的优先级非常低.首先处理输入事件.或者是低级别的黑客攻击:自己使用自己的消息调用PostMessage并覆盖WndProc来检测它.这有点偏离直线和狭窄.Application.Idle事件对于在检索任何输入事件后进行处理非常有用.

  • 我个人尽可能地避免线程,太多的伤疤.我尽可能地使用状态机,*async*关键字正在做什么.我所做的那种编程(机器控制)是相关的.而不是WPF粉丝,不知道Dispatcher.BeginInvoke()的确切细节是否足以跟进.你可以开始另一个关于它的问题. (2认同)