Планирование с помощью EventWaitHandle с помощью Dispatcher.BeginInvoke
Следующий фрагмент кода имеет два потока, каждый из которых пишет 20 string str
в соответствующее текстовое поле. После завершения, Thread t00
сигналы Thread t01
начать и изменить общий string str
от у до х. Thread t00
должен написать 20 лет в текстовое поле, и Thread t01
следует написать 20 х в другое текстовое поле. Вместо, Thread t00
в итоге пишу 19 лет и 1 х. Но если я добавлю Thread.Sleep()
до того, как EventWaitHandle установлен, это решает проблему, которую я имел (я получаю 20 х и 20 лет), но почему? 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();
}
}
2 ответа
Потому что вы используете BeginInvoke
, BeginInvoke
вызывает делегат в потоке пользовательского интерфейса асинхронно. Он помещает сообщение в очередь сообщений потока пользовательского интерфейса и возвращает его, поэтому тот факт, что он возвратил, не означает, что действие было фактически выполнено.
По этой причине, в большинстве случаев, когда вы устанавливаете дескриптор ожидания, а другой поток получает Singal и изменения str
от y
в x
- есть еще не вызванный tb00.AppendText(str);
делегировать в очереди потока пользовательского интерфейса. Когда это наконец вызывается - str
уже x
,
Thread.Sleep
"исправляет" это, потому что дает некоторое время для выполнения этого ожидающего делегата в потоке пользовательского интерфейса. С помощью Invoke
вместо BeginInvoke
также "исправляет" это, потому что Invoke
является синхронным и возвращается только после фактического выполнения делегата в потоке пользовательского интерфейса.
Думать о BeginInvoke()
толкая единицу работы в очередь. Когда 20-го action00
выталкивается из рабочей очереди и выполняется, str='x'
уже выполнено, и поэтому, когда action00
на самом деле запустить, он напечатает x
,
Thread.Sleep(200)
дает 20 action00
время выскочить и бежать до str='x'
выполнить, следовательно, он напечатает y
,