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

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

11 голосов
/ 09 марта 2010

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

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

Есть ли другой способ, которым я должен делать это? Я использую Microsoft Visual C # 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);
   }
}

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

Ответы [ 5 ]

7 голосов
/ 09 марта 2010

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

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

И что, есть приличное введение в управление потоками.

Что лучше всего подходит для вас? Зависит от вашего приложения. Вероятно, лучше , а не , чтобы раскачивать лодку и модифицировать вашу текущую реализацию, чтобы

  1. ваша фоновая задача проверяет и уважает свойство Cancel
  2. ваш основной поток ожидает завершения, в отличие от опроса

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

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

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

4 голосов
/ 09 марта 2010

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

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

4 голосов
/ 09 марта 2010

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

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

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

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

1 голос
/ 09 марта 2010

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

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

0 голосов
/ 09 марта 2010

Попробуйте:

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