Dispatcher Invoke(...) vs BeginInvoke(...) confusion

本文关键字:confusion BeginInvoke vs Dispatcher Invoke | 更新日期: 2023-09-27 18:32:50

我很困惑为什么我不能让这个测试计数器应用程序与 2(或更多(同时运行的计数器框一起工作,并在 Count(( 方法中的调度程序上使用"BeginInvoke"。

您可以通过将 BeginInvoke 替换为 Invoke 来解决此问题。但这并不能解决我的困惑。

这是我正在谈论的示例代码:

public class CounterTextBox : TextBox
{
    private int _number;
    public void Start()
    {
        (new Action(Count)).BeginInvoke(null, null);
    }
    private void Count()
    {
        while (true)
        {
            if (_number++ > 10000) _number = 0;
            this.Dispatcher.BeginInvoke(new Action(UpdateText), System.Windows.Threading.DispatcherPriority.Background, null);    
        }
    }
    private void UpdateText()
    {
        this.Text = "" + _number;
    }
}

Dispatcher Invoke(...) vs BeginInvoke(...) confusion

当您使用 Dispatcher.BeginInvoke 时,这意味着它会计划给定的操作在稍后的时间点在 UI 线程中执行,然后返回控制权以允许当前线程继续执行。 Invoke阻止调用方,直到计划的操作完成。

当您使用BeginInvoke时,您的循环将运行得非常快,因为BeginInvoke会立即返回。 这意味着您要向消息队列添加大量操作。 您添加它们的速度比实际处理它们的速度要快得多。 这意味着在计划消息和实际运行消息之间有很长的时间。

您正在运行的实际操作使用字段_number 。 但是_number正在被另一个线程非常快速地修改,而操作在队列中。 这意味着它不会在您计划操作时显示_number的值,而是在非常紧密的循环中继续运行后显示它是什么。

如果您改用Dispatcher.Invoke,那么它会阻止循环"超越自身"并具有多个计划事件,从而确保它写入的值始终是"当前"值。 此外,通过强制循环的每次迭代等待消息运行,它使循环不那么"紧密",因此它通常不能运行得那么快。

如果你想使用BeginInvoke你真正需要做的第一件事就是放慢你的循环。 如果您希望它每秒或每 10 毫秒或其他时间更新文本,那么您可以使用 Thread.Sleep 等待适当的时间。

接下来,您需要在将 _number 传递给 Dispatcher 之前获取它的副本,以便它在您计划的时间而不是在执行时显示值:

while (true)
{
    if (_number++ > 10000)
        _number = 0;
    int copy = _number;
    this.Dispatcher.BeginInvoke(new Action(() => UpdateText(copy))
        , System.Windows.Threading.DispatcherPriority.Background, null);
    Thread.Sleep(200);
}

private void UpdateText(int number)
{
    this.Text = number.ToString();
}