使用 Dispatcher.BeginInvoke 的 EventWaitHandle 进行调度
Scheduling with EventWaitHandle with Dispatcher.BeginInvoke
下面的代码有两个线程,每个线程都将 20 的 string str
写入其相应的文本框。完成后,Thread t00
通知 Thread t01
开始并将共享 string str
从 y 更改为 x。 Thread 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();
}
}
因为您正在使用 BeginInvoke
。 BeginInvoke
异步调用 UI 线程上的委托。它将消息放入 UI 线程消息队列和 returns,因此它返回的事实并不意味着实际执行了操作。
出于这个原因,在大多数情况下,当您设置等待句柄并且另一个线程收到信号并将 str
从 y
更改为 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
。
下面的代码有两个线程,每个线程都将 20 的 string str
写入其相应的文本框。完成后,Thread t00
通知 Thread t01
开始并将共享 string str
从 y 更改为 x。 Thread 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();
}
}
因为您正在使用 BeginInvoke
。 BeginInvoke
异步调用 UI 线程上的委托。它将消息放入 UI 线程消息队列和 returns,因此它返回的事实并不意味着实际执行了操作。
出于这个原因,在大多数情况下,当您设置等待句柄并且另一个线程收到信号并将 str
从 y
更改为 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
。