使用 Dispatcher.BeginInvoke 的 EventWaitHandle 进行调度

Scheduling with EventWaitHandle with Dispatcher.BeginInvoke

下面的代码有两个线程,每个线程都将 20 的 string str 写入其相应的文本框。完成后,Thread t00 通知 Thread t01 开始并将共享 string stry 更改为 xThread t00 应该将 20 y 写入一个文本框,而 Thread t01 应该将 20 x 写入另一个文本框。相反,Thread t00 最终写成 19 y 和 1 x。但是,如果我在设置 EventWaitHandle 之前添加一个 Thread.Sleep(),这将解决我遇到的问题(我得到 20 x 和 20 y) , 但为什么? EventWaitHandle 不应该只在循环结束后设置,有或没有 Thread.Sleep()

public partial class MainWindow : Window
{
    public string str = "y";
    static EventWaitHandle _waitHandle = new AutoResetEvent(false);

    public MainWindow()
    {
        InitializeComponent();

        Thread t00 = new Thread(() =>
        {
            for (int i = 0; i < 20; i++)
            {
                Thread.Sleep(200);
                Action action00 = () =>
                {
                    tb00.AppendText(str);
                };
                Dispatcher.BeginInvoke(action00);
            }
            Thread.Sleep(200);  // <-- why this fix the problem??
            _waitHandle.Set();
        });
        t00.Start();


        Thread t01 = new Thread(() =>
        {
            Action action00 = () =>
            {
                tb01.AppendText("Waiting...\n");
            };
            Dispatcher.BeginInvoke(action00);
            _waitHandle.WaitOne();

            str = "x";
            for (int i = 0; i < 20; i++)
            {
                Thread.Sleep(200);
                Action action = () =>
                {
                    tb01.AppendText(str);
                };
                Dispatcher.BeginInvoke(action);
            }
        });
        t01.Start();
    }
}

因为您正在使用 BeginInvokeBeginInvoke 异步调用 UI 线程上的委托。它将消息放入 UI 线程消息队列和 returns,因此它返回的事实并不意味着实际执行了操作。

出于这个原因,在大多数情况下,当您设置等待句柄并且另一个线程收到信号并将 stry 更改为 x - 仍然有 non-invoked tb00.AppendText(str); 在 UI 线程队列中委托。当它最终被调用时 - str 已经是 x.

Thread.Sleep "fixes" 那是因为它为挂起的委托提供了一些时间在 UI 线程上执行。使用 Invoke 而不是 BeginInvoke 也是 "fixes",因为 Invoke 是同步的,而 returns 只有在委托实际在 UI 线程上执行之后。

想想 BeginInvoke() 将工作单元推入队列。当第 20 个 action00 从工作队列中弹出并执行时, str='x' 已经执行,所以当 action00 实际上是 运行 时,它会打印一个 x.

Thread.Sleep(200) 给出第 20 个 action00 的弹出时间和 运行 在 str='x' 执行之前,因此它将打印 y