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

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

private void App_FormClosing (отправитель объекта, FormClosingEventArgs e) {backgroundWorker1.CancelAsync (); в то время как (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);
   }
}

Что мне еще нужно делать?


person Jim Fell    schedule 09.03.2010    source источник
comment
Вы никогда не должны запускать цикл опроса, который вращается таким образом, он будет потреблять циклы ЦП, что затрудняет выполнение другими вашими потоками своей работы. Если вы не можете использовать WaitHandle или аналогичный примитив синхронизации и вам абсолютно необходимо провести опрос, добавьте туда Thread.Sleep (x).   -  person Ian Mercer    schedule 09.03.2010
comment
+1 к комментарию @ Hightechrider. Мало того, что ваше приложение не завершится, но и то, что вы не спите, в значительной степени заблокирует всю вашу систему.   -  person Kevin Gale    schedule 09.03.2010
comment
На всякий случай, установили ли вы для свойства CanBeCancel или около того значение true для своего BackgroundWorker?   -  person Will Marcouiller    schedule 09.03.2010
comment
@Will: Да, свойство WorkerSupportsCancellation установлено.   -  person Jim Fell    schedule 09.03.2010
comment
Кажется, что простое выполнение backgroundWorker1.Dispose() решает проблему, поскольку все, что я действительно хочу сделать, - это освободить системные ресурсы, потому что форма закрывается. Есть ли причина, по которой не следует использовать метод члена Dispose в этом случае?   -  person Jim Fell    schedule 09.03.2010
comment
@ Джим Фелл, ну, потому что мне нужны принятые точки ответа? ;) jk, я действительно не вижу ничего плохого в явном вызове Dispose, фреймворк предназначен для работы таким образом, но imo кажется немного небрежным. в идеале мы предпочитаем явное завершение и очистку ресурсов, но это второстепенная проблема, пока она работает.   -  person johnny g    schedule 09.03.2010
comment
Я предполагаю, что использование Dispose убьет фонового рабочего и удалит его из собственного пула потоков. Тем самым вы нарушаете одно из преимуществ наличия пула потоков.   -  person CodingBarfield    schedule 05.07.2010


Ответы (5)


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

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

person Reed Copsey    schedule 09.03.2010
comment
+1 Абсолютно, если только фоновому рабочему потоку не нужно выполнить какую-то очистку перед закрытием. - person Kevin Gale; 09.03.2010
comment
@Kevin: Верно, но поскольку OP просто пытался отменить поток, похоже, что это не так ... - person Reed Copsey; 09.03.2010
comment
Спасибо, Рид. Похоже, это тот случай, когда я делал что-то простое гораздо более сложным, чем нужно. - person Jim Fell; 09.03.2010
comment
@ReedCopsey А как насчет Race on RCW Cleanup? - person techno; 10.04.2012

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

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

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

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

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

person johnny g    schedule 09.03.2010
comment
Вы боитесь набирать while? - person martijnn2008; 05.03.2017

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

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

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

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

person Kevin Gale    schedule 09.03.2010
comment
близко, но не совсем. ваша фоновая задача должна проверять [и уважать!] его свойство DoWorkEventArgs.Cancel. если вы не разделяете переменные экземпляра между потоками - не рекомендуется - фоновая задача не будет иметь доступа к фактическому экземпляру BackgroundWorker. - person johnny g; 09.03.2010
comment
Собственный пример Microsoft обращается к фоновому работнику через параметр отправителя. И в любом случае я ссылался не на его переменную для фонового работника, а на свойство класса. Чтобы было понятнее, я добавил ссылку на пример. - person Kevin Gale; 10.03.2010

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

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

person Hans Passant    schedule 09.03.2010

Пытаться:

if (this.backgroundWorker1.IsBusy) this.backgroundWorker1.CancelAsync();
person Michael D. Irizarry    schedule 09.03.2010