Некоторые довольно хорошие предложения, но я не верю, что они решают основную проблему: отмена фоновой задачи.
К сожалению, при использовании BackgroundWorker
завершение вашей задачи зависит от самой задачи. Единственный способ, которым ваш цикл while
завершится, - это если ваша фоновая задача проверяет свое свойство Cancel
и возвращает или прерывает текущий процесс.
Пример базы
Например, рассмотрим
private readonly BackgroundWorker worker = new BackgroundWorker ();
public void SomeFormEventForStartingBackgroundTask ()
{
worker.DoWork += BackgroundTask_HotelCalifornia;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync ();
}
// semantically, you want to perform this task for lifetime of
// application, you may even expect that calling CancelAsync
// will out and out abort this method - that is incorrect.
// CancelAsync will only set DoWorkEventArgs.Cancel property
// to true
private void BackgroundTask_HotelCalifornia (object sender, DoWorkEventArgs e)
{
for ( ; ;)
{
// because we never inspect e.Cancel, we can never leave!
}
}
private void App_FormClosing(object sender, FormClosingEventArgs e)
{
// [politely] request termination
worker.CancelAsync();
// [politely] wait until background task terminates
while (worker.IsBusy);
}
Это то, что происходит по умолчанию. Теперь, возможно, ваша задача не является бесконечным циклом, возможно, это просто длительная задача. В любом случае, ваш основной поток будет блокировать [на самом деле он вращается, но без изменений] до тех пор, пока задача не будет завершена, или нет в зависимости от обстоятельств.
Если вы лично написали и можете изменить задание, у вас есть несколько вариантов.
Пример улучшения
Например, это лучшая реализация вышеприведенного примера
private readonly BackgroundWorker worker = new BackgroundWorker ();
// this is used to signal our main Gui thread that background
// task has completed
private readonly AutoResetEvent isWorkerStopped =
new AutoResentEvent (false);
public void SomeFormEventForStartingBackgroundTask ()
{
worker.DoWork += BackgroundTask_HotelCalifornia;
worker.RunWorkerCompleted += BackgroundTask_Completed;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync ();
}
private void BackgroundTask_HotelCalifornia (object sender, DoWorkEventArgs e)
{
// execute until canceled
for ( ; !e.Cancel;)
{
// keep in mind, this task will *block* main
// thread until cancel flag is checked again,
// so if you are, say crunching SETI numbers
// here for instance, you could still be blocking
// a long time. but long time is better than
// forever ;)
}
}
private void BackgroundTask_Completed (
object sender,
RunWorkerCompletedEventArgs e)
{
// ok, our task has stopped, set signal to 'signaled' state
// we are complete!
isStopped.Set ();
}
private void App_FormClosing(object sender, FormClosingEventArgs e)
{
// [politely] request termination
worker.CancelAsync();
// [politely] wait until background task terminates
isStopped.WaitOne ();
}
Хотя это и лучше, но не так хорошо, как могло бы быть. Если вы можете быть [разумно] уверены, что ваша фоновая задача закончится, это может быть «достаточно хорошо».
Однако то, что мы [обычно] хотим, это что-то вроде этого
private void App_FormClosing(object sender, FormClosingEventArgs e)
{
// [politely] request termination
worker.CancelAsync();
// [politely] wait until background task terminates
TimeSpan gracePeriod = TimeSpan.FromMilliseconds(100);
bool isStoppedGracefully = isStopped.WaitOne (gracePeriod);
if (!isStoppedGracefully)
{
// KILL! KILL! KILL!
}
}
Увы, мы не можем. BackgroundWorker
не предоставляет никаких средств насильственного прекращения. Это потому, что это абстракция, построенная поверх некоторой скрытой системы управления потоками, которая потенциально может дестабилизировать другие части вашего приложения, если она будет принудительно завершена.
Единственный способ [который я видел по крайней мере] для реализации вышеизложенного - это управлять своими собственными потоками.
Пример идеального
Так, например
private Thread worker = null;
// this time, 'Thread' provides all synchronization
// constructs required for main thread to synchronize
// with background task. however, in the interest of
// giving background task a chance to terminate gracefully
// we supply it with this cancel signal
private readonly AutoResetEvent isCanceled = new AutoResentEvent (false);
public void SomeFormEventForStartingBackgroundTask ()
{
worker = new Thread (BackgroundTask_HotelCalifornia);
worker.IsBackground = true;
worker.Name = "Some Background Task"; // always handy to name things!
worker.Start ();
}
private void BackgroundTask_HotelCalifornia ()
{
// inspect cancel signal, no wait period
//
// NOTE: so cheating here a bit, this is an instance variable
// but could as easily be supplied via parameterized thread
// start delegate
for ( ; !isCanceled.WaitOne (0);)
{
}
}
private void App_FormClosing(object sender, FormClosingEventArgs e)
{
// [politely] request termination
isCanceled.Set ();
// [politely] wait until background task terminates
TimeSpan gracePeriod = TimeSpan.FromMilliseconds(100);
bool isStoppedGracefully = worker.Join (gracePeriod);
if (!isStoppedGracefully)
{
// wipe them out, all of them.
worker.Abort ();
}
}
И что, есть приличное введение в управление потоками.
Что лучше всего подходит для вас? Зависит от вашего приложения. Вероятно, лучше , а не , чтобы раскачивать лодку и модифицировать вашу текущую реализацию, чтобы
- ваша фоновая задача проверяет и уважает свойство
Cancel
- ваш основной поток ожидает завершения, в отличие от опроса
Очень важно сравнивать и оценивать плюсы и минусы каждого подхода.
Если вы должны контролировать и гарантировать завершение чьих-либо других задач , то, возможно, стоит написать систему управления потоками, которая включает в себя все вышеперечисленное. Тем не менее, вы бы упустили такие встроенные функции, как пул потоков, отчеты о прогрессе, межпотоковое маршалинг данных [работник делает это, нет?] И кучу других вещей. Не говоря уже о том, что «сворачивание своего» часто приводит к ошибкам.
В любом случае, надеюсь, это поможет:)