Прежде всего, решите, нужно ли вам вообще использовать Invoke
. Как и раньше, мое мнение о том, что Invoke
является одним из наиболее часто используемых методов, побуждающих к выполнению действия в потоке пользовательского интерфейса после его инициирования из рабочего потока. Альтернативой является создание некоторого типа объекта сообщения в рабочем потоке, описывающего действие, которое необходимо выполнить в потоке пользовательского интерфейса, и помещение его в общую очередь. Затем поток пользовательского интерфейса будет опрашивать эту очередь через некоторый интервал, используя System.Windows.Forms.Timer
, и вызывать каждое действие. Это имеет несколько преимуществ.
- Это нарушает тесную связь между пользовательским интерфейсом и рабочими потоками, которые навязывает
Invoke
.
- Он возлагает ответственность за обновление потока пользовательского интерфейса на поток пользовательского интерфейса, к которому он все равно должен принадлежать.
- Поток пользовательского интерфейса определяет, когда и как часто должно происходить обновление.
- Нет риска переполнения насоса сообщений пользовательского интерфейса, как в случае с методами маршалинга, инициированными рабочим потоком.
- Рабочему потоку не нужно ждать подтверждения того, что обновление было выполнено, прежде чем переходить к следующим шагам (т. Е. Вы получаете большую пропускную способность как для пользовательского интерфейса, так и для рабочих потоков).
- На много проще обрабатывать операции выключения, поскольку вы можете практически полностью исключить все состояния гонки, которые обычно присутствуют при запросе на корректное завершение рабочих потоков.
Конечно, Invoke
очень полезен, и есть много причин, чтобы продолжать использовать этот подход. Если вы решите продолжать использовать Invoke
, тогда читайте дальше.
Одна из идей - установить флаги KillThreads
в событии Form.Closing
, а затем отменить закрытие формы, установив FormClosingEventArgs.Cancel = true
. Возможно, вы захотите сообщить пользователю, что завершение работы было запрошено и выполняется. Вы можете отключить некоторые элементы управления в форме, чтобы новые действия не могли быть инициированы. Таким образом, в основном мы сигнализируем, что было запрошено завершение работы, но откладываем закрытие формы до тех пор, пока рабочие потоки не завершат сначала. Для этого вы можете запустить таймер, который периодически проверяет, закончились ли рабочие потоки, и если они есть, вы можете вызвать Form.Close
.
public class YourForm : Form
{
private Thread WorkerThread;
private volatile bool KillThreads = false;
private void YourForm_Closing(object sender, FormClosingEventArgs args)
{
// Do a fast check to see if the worker thread is still running.
if (!WorkerThread.Join(0))
{
args.Cancel = true; // Cancel the shutdown of the form.
KillThreads = true; // Signal worker thread that it should gracefully shutdown.
var timer = new System.Timers.Timer();
timer.AutoReset = false;
timer.SynchronizingObject = this;
timer.Interval = 1000;
timer.Elapsed =
(sender, args) =>
{
// Do a fast check to see if the worker thread is still running.
if (WorkerThread.Join(0))
{
// Reissue the form closing event.
Close();
}
else
{
// Keep restarting the timer until the worker thread ends.
timer.Start();
}
};
timer.Start();
}
}
}
Приведенный выше код вызывает Join
, но он указывает время ожидания 0, которое вызывает немедленный возврат вызова Join
. Это должно поддерживать прокачку сообщений пользовательского интерфейса.