使用BeginInvoke调用的Net任务在主窗体上未执行

本文关键字:窗体 执行 任务 BeginInvoke 调用 Net 使用 | 更新日期: 2023-09-27 18:17:31

我使用Visual Studio 2013构建了一个带有单个表单的c#应用程序,该应用程序有两个更新屏幕的例程。更新屏幕的例程需要在主线程上运行,所以我自己的线程(不与屏幕交互)在需要更新时调用主窗体上的BeginInvoke方法。但是,应用程序中的某个地方发生了一些事情,导致两个更新例程停止执行。我已经把登录到应用程序跟踪调用BeginInvoke和更新例程的执行,我可以看到,当这个问题发生时,BeginInvoke调用,但然后什么也没有。当这种情况发生时,整个应用程序似乎会冻结。我想不出是什么原因造成的。我如何调试这个?有没有办法查看在主线程上排队运行的是什么?当我在调试中运行并进入应用程序时,所有线程看起来都很正常,主线程似乎没有做任何事情,那么为什么它不处理挂起的更新任务呢?

使用BeginInvoke调用的Net任务在主窗体上未执行

Control.BeginInvoke()将委托添加到内部线程安全队列中。并向UI线程发送消息告诉它去查看那个队列。Application.Run()中的消息循环获取该消息并再次清空队列,执行委托。

所以如果你没有看到这种情况发生,那么最明显的原因是UI线程不在Application.Run()循环中。例如,您可能犯的一个标准错误是等待线程完成。很可能导致死锁。永远不要等待,如果你需要在线程完成后运行代码,那么考虑BackgroundWorker的RunWorkerCompleted事件或TaskScheduler.FromCurrentSynchronizationContext()。

看不到任何事情发生的不太明显的失败模式是你太频繁地调用BeginInvoke()。如果您每秒执行此操作超过1000次,那么您将用太多的委托淹没内部队列。UI线程会忙着清空那个队列,但永远赶不上,总是在队列中找到另一个执行后的委托。当这种情况发生时,它就会变得紧张,不再履行正常的职责。比如响应输入和粉刷窗户。除了限制调用BeginInvoke()的频率之外,没有修复这个问题。一定要记住目标,你只需要在用户的眼睛能感知到的情况下经常这样做。以每秒超过25次的速度更新UI只是浪费精力。

这可能是由于两个更新例程试图同时更新UI。我见过奇怪的UI行为,例如部分更新的控件,当多个交错事件触发许多UI更新时,在短时间内发生。这两个例程是不同的例程,对吗?

解决这个问题的一个可能的方法是在UI线程上使用异步委托调用。在下面的代码中,我假设你的UI是一个WinForms表单,我已经命名了两个例程UpdateAUpdateB

private bool isUpdating;
public delegate void UpdateDelegate();
private void UpdateA()
{
    if (isUpdating)
    {
        this.BeginInvoke(new UpdateDelegate(UpdateA));
    }
    else
    {
        isUpdating = true;
        try
        {
            // ... do UI updates for A
        }
        finally
        {
            isUpdating = false;
        }
    }
}
private void UpdateB()
{
    if (isUpdating)
    {
        this.BeginInvoke(new UpdateDelegate(UpdateB));
    }
    else
    {
        isUpdating = true;
        try
        {
            // ... do UI updates for B
        }
        finally
        {
            isUpdating = false;
        }
    }
}
顺便说一下,我没有使用上面的lock来同步访问标志isUpdating,假设UpdateA和UpdateB都在UI线程上执行。它们由工作线程通过BeginInvoke异步调用。