WinForms消息循环无响应

时间:2011-07-22 11:09:08

标签: 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);
    }
}

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

有什么建议吗?

3 个答案:

答案 0 :(得分:17)

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

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

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

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

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

答案 1 :(得分:0)

尝试添加对

的调用
Application.DoEvents()

你的ContinueWith()方法中的somwhere。

请不要在实验之外的任何地方使用这种“穿线”风格。 )。

来源:https://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents(v=vs.110).aspx

答案 2 :(得分:0)

使用优先级优先的begininvoke重载。 “正常”优先级高于输入和渲染。你需要选择像applicationidle

这样的东西