Отмена фоновых задач

Когда мое приложение С# закрывается, оно иногда попадает в процедуру очистки. В частности, фоновый работник не закрывается. Это в основном, как я пытаюсь закрыть его:

  private void App_FormClosing (отправитель объекта, FormClosingEventArgs e)   {      backgroundWorker1.CancelAsync();      while (backgroundWorker1.IsBusy);//Задерживается здесь.   } Забастовкa >

Есть ли другой способ, которым я должен это делать? Я использую Microsoft Visual С# 2008 Express Edition. Спасибо.

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:

Фоновый работник не появляется. Это то, что у меня есть:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
   while (!backgroundWorker1.CancellationPending)
   {
      // Do something.
   }
}

Я также изменил код очистки:

private void App_FormClosing(object sender, FormClosingEventArgs e)
{
   while (backgroundWorker1.IsBusy)
   {
      backgroundWorker1.CancelAsync();
      System.Threading.Thread.Sleep(1000);
   }
}

Есть ли что-то еще, что я должен делать?

Ответ 1

Кевин Гейл прав, заявив, что ваш обработчик DoWork BackgroundWorker должен опросить CancellationPending и вернуть, если требуется аннулирование.

При этом, если это происходит, когда ваше приложение закрывается, вы можете просто игнорировать его безопасно. BackgroundWorker использует поток ThreadPool, который по определению является фоновым потоком. Если вы оставите этот запуск, это не остановит ваше приложение от завершения, и поток будет автоматически снесен, когда ваше приложение отключится.

Ответ 2

Некоторые довольно хорошие предложения, но я не думаю, что они обращаются к основной проблеме: отмена фоновой задачи.

К сожалению, при использовании 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);
}

Это то, что происходит по умолчанию. Теперь, может быть, ваша задача - не бесконечный цикл, возможно, это просто долгосрочная задача. В любом случае ваш основной поток будет блокировать [на самом деле он вращается, но whatevs], пока задача не завершится или не будет в порядке.

Если вы лично написали и можете изменить задачу, у вас есть несколько вариантов.

Улучшение примера

Например, это более эффективная реализация приведенного выше примера

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
  • ваш основной поток ждет завершения, в отличие от опроса

Очень важно сравнивать и оценивать плюсы и минусы каждого подхода.

Если вы должны контролировать и гарантировать завершение чужих задач, то может быть способ записи системы управления потоками, которая включает в себя вышеупомянутое. Тем не менее, вы потеряете свои функции, такие как объединение потоков, отчет о достигнутых результатах, сортировка данных по перекрестным потокам [рабочий делает это, нет?] И множество других вещей. Не говоря уже о том, что "сворачивание собственных" часто подвержено ошибкам.

В любом случае, надеюсь, что это поможет:)

Ответ 3

В фоновом потоке рабочего процесса вам нужно проверить флаг BackgroundWorker.CancellationPending и выйти, если это правда.

Функция CancelAsync() просто устанавливает этот флаг.

Или сказать иначе. CancelAsync() фактически ничего не отменяет. Он не прервет нить или не выйдет из нее. Если рабочий поток находится в цикле и периодически проверяет флаг CancellationPending, он может поймать запрос отмены и выйти.

В MSDN приведен пример здесь, хотя он не использует цикл в рабочей процедуре.

Ответ 4

Этот код гарантированно заторможен, когда BGW все еще работает. BGW не может завершить работу до завершения работы RunWorkerCompleted. RunWorkerCompleted не может работать до тех пор, пока поток пользовательского интерфейса не станет бездействовать и запустит цикл сообщений. Но поток UI не простаивает, он застревает в цикле while.

Если вы хотите, чтобы поток BGW завершился чисто, вы должны сохранить свою форму в живых. Проверьте этот поток, чтобы узнать, как это сделать.

Ответ 5

Try:

if (this.backgroundWorker1.IsBusy) this.backgroundWorker1.CancelAsync();