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

c# winforms backgroundworker

4335 просмотра

5 ответа

Когда мое приложение 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);
   }
}

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

Автор: Jim Fell Источник Размещён: 12.11.2019 09:18

Ответы (5)


4 плюса

Решение

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

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

Автор: Reed Copsey Размещён: 09.03.2010 06:23

7 плюса

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

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

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

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

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

Автор: johnny g Размещён: 09.03.2010 07:06

4 плюса

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

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

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

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

Автор: Kevin Gale Размещён: 09.03.2010 06:10

1 плюс

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

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

Автор: Hans Passant Размещён: 09.03.2010 07:01

0 плюса

Пытаться:

if (this.backgroundWorker1.IsBusy) this.backgroundWorker1.CancelAsync();
Автор: Michael D. Irizarry Размещён: 09.03.2010 06:14
Вопросы из категории :
32x32