38

我很困惑为什么我不能使用 Count() 方法中的 Dispatcher 上的“BeginInvoke”使这个测试计数器应用程序与 2 个(或更多)同时运行的计数器文本框一起工作。

您可以通过将 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;
    }
}
4

1 回答 1

101

当您使用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();
}
于 2013-09-25T15:51:53.227 回答