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

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

частный 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);
   }
}

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

11
задан Uwe Keim 28 July 2017 в 06:36
поделиться

5 ответов

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

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

4
ответ дан 3 December 2019 в 07:37
поделиться

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

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

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

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

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

7
ответ дан 3 December 2019 в 07:37
поделиться

Попробуйте:

if (this.backgroundWorker1.IsBusy) this.backgroundWorker1.CancelAsync();
0
ответ дан 3 December 2019 в 07:37
поделиться

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

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

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

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

4
ответ дан 3 December 2019 в 07:37
поделиться

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

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

1
ответ дан 3 December 2019 в 07:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: