Dispatcher Invoke(...)与BeginInvoke(...)混淆问题

43

我很困惑为什么在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;
    }
}
1个回答

110
当你使用Dispatcher.BeginInvoke时,它意味着在稍后的时间点将给定的操作安排在UI线程中执行,然后返回控件以允许当前线程继续执行。而Invoke会阻塞调用者直到计划的操作完成。
当你使用BeginInvoke时,由于BeginInvoke立即返回,因此你的循环会运行非常快。这意味着你正在向消息队列中添加大量的操作。你添加它们的速度远远快于它们实际可以被处理的速度。这意味着在计划消息和实际运行消息之间存在很长的时间差。
实际运行的操作使用字段_number。但是_number会被另一个线程非常快地修改,并且当操作在队列中时。这意味着它不会显示您计划操作时的_number值,而是在其紧密循环中持续运行后的值。
如果改用Dispatcher.Invoke,则可以防止循环“超前”,并有多个计划事件,从而确保编写的值始终为“当前”值。此外,通过强制等待消息运行的每次迭代,使循环更加松散,因此通常无法运行得如此快。
如果要使用BeginInvoke,则首先需要做的是减慢循环速度。如果你想每秒更新文本一次,或每10ms更新,或其他什么时候,那么可以使用Thread.Sleep等待适当的时间量。
接下来,您需要在传递给Dispatcher之前复制_number,以便它显示计划时的值,而不是执行时的值:
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();
}

1
非常好的描述。+1 提醒人们复制传递到线程边界的变量。 - Jesse Chisholm
@JesseChisholm 这不是关于在线程边界传递值,而是关于复制正在关闭的变量的值,当您希望其语义是基于值而不是基于变量进行闭包时。推迟执行一些代码的闭包并不需要涉及多个线程,尽管在这种特定情况下确实会发生。 - Servy
好的。同意。需要考虑的另一件事是,BeginInvoke有时需要一个平衡的EndInvoke。详情请参见:https://dev59.com/h3M_5IYBdhLWcg3wrFMt。但在这种情况下,`Control.BeginInvoke`(因此也包括`Form.BeginInvoke`)被记录为不需要`EndInvoke`的特殊情况。 - Jesse Chisholm
2
Dispatcher 没有实现 ISynchronizeInvoke。虽然这个方法的名字和那个方法相同,但在语义上明显不同。 - Servy
现在我明白了你的解释中Invoke和BeginInvoke的区别。非常好的解释+1。 - Luiey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接