Может ли это вызвать тупик? BeginInvoke() & thread.Join()
У меня есть этот код, который многие потоки могут вызывать для обновления графического интерфейса:
MethodInvoker del = () => { lblInfo.Text = tmp; };
lblInfo.BeginInvoke(del);
(lblInfo создается потоком GUI)
У меня также есть этот метод, вызываемый при нажатии кнопки, выполняемый потоком GUI:
public void Stop()
{
isStopping = true;
crawler.Join();
foreach (Thread t in txtWorkers)
{
t.Join();
}
indexer.Join();
lblStatus.Text = "Stopped";
lblInfo.Text = "";
}
1 раз за 100 запустите программу тупиком при нажатии кнопки Стоп. Я не отлаживал, когда увидел тупик, поэтому не могу быть уверен в состоянии различных потоков, но я почти уверен, что все потоки, к которым я присоединяюсь, в конечном итоге достигнут точки, где они проверяют isStopping
оценить и прекратить. Это заставляет меня думать, что может быть проблема с BeginInvoke
но не могу найти его Он должен быть асинхронным, поэтому потоки, вызывающие его (сканер и индексатор), не должны блокироваться. Что происходит, если выполняется поток графического интерфейса Stop()
а также должен выполнить звонок от BeginInvoke
? Может ли это быть проблемой? Есть что-то, чего я не вижу в теме, к которой я присоединяюсь?
РЕДАКТИРОВАТЬ: Как выглядит код после предложенных изменений:
public void Stop()
{
/*
...disable GUI
*/
isStopping = true; // Declared as volatile
lblStatus.Text = "Stopping...";
// Creating a thread that will wait for other threads to terminate
Task.Factory.StartNew(() =>
{
crawler.Join();
foreach (Thread t in txtWorkers)
{
t.Join();
}
indexer.Join();
// Adjust UI now that all threads are terminated
MethodInvoker del = () =>
{
/*
...enable GUI
*/
lblStatus.Text = "Not Running";
isStopping = false;
};
lblStatus.BeginInvoke(del);
});
}
Кажется, что работает, я надеюсь, что тупик ушел...
1 ответ
Я не думаю, что это должно быть проблемой, потому что вы используете BeginInvoke
скорее, чем Invoke
- фоновые потоки будут просто проходить мимо этой линии, не дожидаясь, пока графический интерфейс нагонит их. Если вы используете Control.Invoke
в любом месте, это может привести к тупику.
Что еще более важно, используя Join
в вашем потоке GUI принципиально плохая идея - пользовательский интерфейс будет заморожен, пока все не закончится. Было бы лучше отключить все элементы управления, которые могут начать что-то новое, установите isStopping
установите флажок, а затем создайте новый поток, чтобы дождаться остановки всех потоков, а после завершения всех потоков обновите пользовательский интерфейс с помощью BeginInvoke
снова. (Если вы используете.NET 4.5, вы также можете использовать асинхронный метод для этого, создавая и ожидая задачу для ожидания всех потоков.)
Наконец, если isStopping
это просто bool
поле, нет никакой гарантии, что ваши фоновые потоки "увидят" изменения из потока пользовательского интерфейса. Возможно, что исправление поля volatile исправит это, но точное значение volatile пугает меня. Альтернативой было бы использовать Interlocked
класса, или сделайте его свойством, которое получает блокировку как для чтения, так и для записи, что гарантирует наличие соответствующих барьеров памяти.