Как дождаться отмены BackgroundWorker?

125

Рассмотрим гипотетический метод объекта, который делает что-то за вас:

public class DoesStuff
{
    BackgroundWorker _worker = new BackgroundWorker();

    ...

    public void CancelDoingStuff()
    {
        _worker.CancelAsync();

        //todo: Figure out a way to wait for BackgroundWorker to be cancelled.
    }
}

Как можно дождаться завершения BackgroundWorker?


В прошлом люди пробовали:

while (_worker.IsBusy)
{
    Sleep(100);
}

Но это взаимоблокировки , потому что IsBusyне очищается до тех пор, пока RunWorkerCompletedсобытие не будет обработано, и это событие не может быть обработано, пока приложение не перейдет в режим ожидания. Приложение не будет бездействовать, пока рабочий не закончит работу. (Плюс, это замкнутый круг - отвратительно.)

Другие добавили, что предлагается включить его в:

while (_worker.IsBusy)
{
    Application.DoEvents();
}

Проблема в том, что это Application.DoEvents()вызывает обработку сообщений, находящихся в настоящее время в очереди, что вызывает проблемы повторного входа (.NET не повторяется).

Я бы надеялся использовать какое-то решение, включающее объекты синхронизации событий, где код ожидает события, которое рабочийRunWorkerCompleted устанавливает обработчик событий . Что-то вроде:

Event _workerDoneEvent = new WaitHandle();

public void CancelDoingStuff()
{
    _worker.CancelAsync();
    _workerDoneEvent.WaitOne();
}

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    _workerDoneEvent.SetEvent();
}

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

Итак, как вы можете дождаться завершения BackgroundWorker?


Обновление Люди, кажется, сбиты с толку этим вопросом. Кажется, они думают, что я буду использовать BackgroundWorker как:

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);

Это не то, это не то, что я делаю, и не об этом здесь спрашивают. Если бы это было так, не было бы смысла использовать фонового воркера.

Ян Бойд
источник

Ответы:

130

Если я правильно понимаю ваше требование, вы можете сделать что-то вроде этого (код не тестировался, но показывает общую идею):

private BackgroundWorker worker = new BackgroundWorker();
private AutoResetEvent _resetEvent = new AutoResetEvent(false);

public Form1()
{
    InitializeComponent();

    worker.DoWork += worker_DoWork;
}

public void Cancel()
{
    worker.CancelAsync();
    _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    while(!e.Cancel)
    {
        // do something
    }

    _resetEvent.Set(); // signal that worker is done
}
Фредрик Калсет
источник
7
Это заблокирует пользовательский интерфейс (например, без перерисовки), если фоновый рабочий процесс отменит много времени. Чтобы прекратить взаимодействие с пользовательским интерфейсом, лучше использовать одно из следующих действий: stackoverflow.com/questions/123661/…
Джо,
1
+1 именно то, что доктор прописал ... хотя я согласен с @Joe, если запрос на отмену может занять больше секунды.
dotjoe
Что происходит, когда CancelAsync обрабатывается до WaitOne? Или кнопка «Отмена» работает только один раз.
CodingBarfield
6
Мне нужно было проверить, ((BackgroundWorker)sender).CancellationPendingчтобы получить событие отмены
Луук
1
Как упоминал Луук, необходимо проверять не свойство Cancel, а CancellationPending. Во-вторых, как упоминает Джоэл Кохорн, ожидание завершения потока в первую очередь сводит на нет цель использования потока.
Captain Sensible
15

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

Второй недостаток заключается в том, что _resetEvent.Set()он никогда не будет вызван, если рабочий поток генерирует исключение, оставляя основной поток ожидающим на неопределенное время, однако этот недостаток можно легко исправить с помощью блока try / finally.

Один из способов сделать это - отобразить модальное диалоговое окно с таймером, который неоднократно проверяет, завершил ли фоновый рабочий процесс работу (или завершил ли ее отмену в вашем случае). Как только фоновый исполнитель завершит работу, модальное диалоговое окно вернет управление вашему приложению. Пока это не произойдет, пользователь не сможет взаимодействовать с пользовательским интерфейсом.

Другой метод (при условии, что у вас открыто не более одного немодального окна) - установить ActiveForm.Enabled = false, затем зацикливаться на Application, DoEvents, пока фоновый рабочий не завершит отмену, после чего вы можете снова установить ActiveForm.Enabled = true.

Джо
источник
5
Это может быть проблемой, но это принципиально принято как часть вопроса «Как дождаться отмены BackgroundWorker». Ожидание означает, что вы ждете и больше ничего не делаете. Это также включает обработку сообщений. Если бы я не хотел ждать фонового рабочего, вы могли бы просто вызвать .CancelAsync. Но это не требование дизайна здесь.
Ian Boyd
1
+1 за указание значения метода CancelAsync, стихи, ожидающие фонового рабочего.
Ian Boyd
10

Почти всех вас смущает этот вопрос, и вы не понимаете, как используется воркер.

Рассмотрим обработчик событий RunWorkerComplete:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;
}

И все хорошо.

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

private void BlowUpRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    StartClaxon();
    SelfDestruct();
}

Также существует ситуация, когда нам нужно открыть ворота доступа к ракете, но не во время обратного отсчета:

private void OpenAccessGates()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

И, наконец, нам нужно слить топливо из ракеты, но это не допускается во время обратного отсчета:

private void DrainRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Без возможности дождаться отмены рабочего процесса, мы должны переместить все три метода в RunWorkerCompletedEvent:

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;

    if (delayedBlowUpRocket)
        BlowUpRocket();
    else if (delayedOpenAccessGates)
        OpenAccessGates();
    else if (delayedDrainRocket)
        DrainRocket();
}

private void BlowUpRocket()
{
    if (worker != null)
    {
        delayedBlowUpRocket = true;
        worker.CancelAsync();
        return;
    }

    StartClaxon();
    SelfDestruct();
}

private void OpenAccessGates()
{
    if (worker != null)
    {
        delayedOpenAccessGates = true;
        worker.CancelAsync();
        return;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

private void DrainRocket()
{
    if (worker != null)
    {
        delayedDrainRocket = true;
        worker.CancelAsync();
        return;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Теперь я мог бы написать свой код таким образом, но я просто не собираюсь. Мне все равно, просто нет.

Ян Бойд
источник
18
Где находится метод WaitForWorkerToFinish? любой полный исходный код?
Kiquenet
4

Вы можете проверить RunWorkerCompletedEventArgs в RunWorkerCompletedEventHandler, чтобы узнать, каков был статус. Успех, отменен или ошибка.

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    if(e.Cancelled)
    {
        Console.WriteLine("The worker was cancelled.");
    }
}

Обновление : чтобы узнать, вызвал ли ваш работник .CancelAsync (), используя это:

if (_worker.CancellationPending)
{
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}
Себ Нильссон
источник
2
Требование состоит в том, что CancelDoingStuff () не может вернуться, пока рабочий не будет завершен. Проверять, как это закончилось, действительно не представляет интереса.
Ян Бойд
Тогда вам нужно будет создать событие. Это действительно не имеет ничего общего с BackgroundWorker, вам просто нужно реализовать событие, прослушать его и запустить, когда закончите. И RunWorkerCompletedEventHandler - когда это будет сделано. Запустить другое событие.
Себ Нильссон,
4

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

Если вы хотите дождаться завершения чего-либо, используйте другую конструкцию потоков, которая предоставляет WaitHandle.

Джоэл Кохорн
источник
1
Вы можете предложить один? Фоновый рабочий процесс кажется единственной потоковой конструкцией, которая может отправлять уведомления потоку, создавшему объект BackgroundWorker.
Ян Бойд
Backgroundworker - это конструкция пользовательского интерфейса . Он просто использует события, которые ваш собственный код все еще должен знать, как вызывать. Ничто не мешает вам создавать для этого собственных делегатов.
Джоэл Кохорн,
3

Почему нельзя просто привязать событие BackgroundWorker.RunWorkerCompleted? Это обратный вызов, который «Произойдет, когда фоновая операция завершена, была отменена или вызвала исключение».

Рик Минерих
источник
1
Потому что человек, использующий объект DoesStuff, попросил его отменить. Общие ресурсы, которые использует объект, вот-вот будут отключены, удалены, утилизированы, закрыты, и он должен знать, что это сделано, чтобы я мог продолжить.
Ian Boyd
1

Я не понимаю, почему вам нужно ждать завершения BackgroundWorker; это действительно похоже на полную противоположность мотивации класса.

Однако вы можете запустить каждый метод с вызова worker.IsBusy и заставить их выйти, если он работает.

Остин Салонен
источник
Плохой подбор слов; я не жду его завершения.
Ян Бойд,
1

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

foreach(DataRow rw in dt.Rows)
{
     //loop code
     while(!backgroundWorker1.IsBusy)
     {
         backgroundWorker1.RunWorkerAsync();
     }
}

Просто подумал, что поделюсь, потому что именно здесь я оказался в поисках решения. Кроме того, это мой первый пост о переполнении стека, поэтому, если это плохо или что-то еще, я бы хотел критиков! :)

Коннор Уильямс
источник
0

Хм, может, я неправильно понял твой вопрос.

Backgroundworker вызывает событие WorkerCompleted после того, как его «workermethod» (метод / функция / подпрограмма, которая обрабатывает событие backgroundworker.doWork ) завершается, поэтому нет необходимости проверять, работает ли BW. Если вы хотите остановить своего рабочего, проверьте свойство ожидания отмены внутри вашего «рабочего метода».

Stephan
источник
Я понимаю, что работник должен отслеживать свойство CancellationPending и как можно скорее выйти. Но как человек снаружи, который запросил отмену фонового работника, ждать, пока это будет сделано?
Ян Бойд,
Событие WorkCompleted по-прежнему будет срабатывать.
Джоэл Кохорн,
«Но как может человек снаружи, который запросил отмену фонового работника, ждать, пока это будет сделано?»
Ян Бойд,
Вы ждете, пока это будет сделано, ожидая срабатывания события WorkCompleted. Если вы хотите, чтобы у пользователя не было щелчка по всему графическому интерфейсу, вы можете использовать одно из решений, предложенных @Joe в его ответе выше (отключите форму или покажите что-нибудь модальное). Другими словами, позвольте циклу простоя системы ждать за вас; он сообщит вам, когда он закончится (запустив событие).
Джефф
0

Рабочий процесс BackgroundWorkerобъекта в основном требует, чтобы вы обрабатывали RunWorkerCompletedсобытие как для нормального выполнения, так и для вариантов использования отмены пользователем. Вот почему свойство RunWorkerCompletedEventArgs.Cancelled существует . По сути, правильное выполнение этого требует, чтобы вы считали ваш метод Cancel асинхронным методом.

Вот пример:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;

namespace WindowsFormsApplication1
{
    public class AsyncForm : Form
    {
        private Button _startButton;
        private Label _statusLabel;
        private Button _stopButton;
        private MyWorker _worker;

        public AsyncForm()
        {
            var layoutPanel = new TableLayoutPanel();
            layoutPanel.Dock = DockStyle.Fill;
            layoutPanel.ColumnStyles.Add(new ColumnStyle());
            layoutPanel.ColumnStyles.Add(new ColumnStyle());
            layoutPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
            layoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));

            _statusLabel = new Label();
            _statusLabel.Text = "Idle.";
            layoutPanel.Controls.Add(_statusLabel, 0, 0);

            _startButton = new Button();
            _startButton.Text = "Start";
            _startButton.Click += HandleStartButton;
            layoutPanel.Controls.Add(_startButton, 0, 1);

            _stopButton = new Button();
            _stopButton.Enabled = false;
            _stopButton.Text = "Stop";
            _stopButton.Click += HandleStopButton;
            layoutPanel.Controls.Add(_stopButton, 1, 1);

            this.Controls.Add(layoutPanel);
        }

        private void HandleStartButton(object sender, EventArgs e)
        {
            _stopButton.Enabled = true;
            _startButton.Enabled = false;

            _worker = new MyWorker() { WorkerSupportsCancellation = true };
            _worker.RunWorkerCompleted += HandleWorkerCompleted;
            _worker.RunWorkerAsync();

            _statusLabel.Text = "Running...";
        }

        private void HandleStopButton(object sender, EventArgs e)
        {
            _worker.CancelAsync();
            _statusLabel.Text = "Cancelling...";
        }

        private void HandleWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                _statusLabel.Text = "Cancelled!";
            }
            else
            {
                _statusLabel.Text = "Completed.";
            }

            _stopButton.Enabled = false;
            _startButton.Enabled = true;
        }

    }

    public class MyWorker : BackgroundWorker
    {
        protected override void OnDoWork(DoWorkEventArgs e)
        {
            base.OnDoWork(e);

            for (int i = 0; i < 10; i++)
            {
                System.Threading.Thread.Sleep(500);

                if (this.CancellationPending)
                {
                    e.Cancel = true;
                    e.Result = false;
                    return;
                }
            }

            e.Result = true;
        }
    }
}

Если вы действительно не хотите, чтобы ваш метод завершился, я бы предложил поставить флаг, например, AutoResetEventна производный BackgroundWorker, а затем переопределить, OnRunWorkerCompletedчтобы установить флаг. Хотя это все еще немного неуклюже; Я бы рекомендовал рассматривать событие отмены как асинхронный метод и делать то, что он делает в данный момент, в RunWorkerCompletedобработчике.

OwenP
источник
Перемещение кода в RunWorkerCompleted - это не то место, где он должен быть, и это некрасиво.
Ян Бойд,
0

Я немного опоздал на вечеринку (около 4 лет), но как насчет настройки асинхронного потока, который может обрабатывать цикл занятости без блокировки пользовательского интерфейса, а затем обратный вызов из этого потока будет подтверждением того, что BackgroundWorker завершил отмену ?

Что-то вроде этого:

class Test : Form
{
    private BackgroundWorker MyWorker = new BackgroundWorker();

    public Test() {
        MyWorker.DoWork += new DoWorkEventHandler(MyWorker_DoWork);
    }

    void MyWorker_DoWork(object sender, DoWorkEventArgs e) {
        for (int i = 0; i < 100; i++) {
            //Do stuff here
            System.Threading.Thread.Sleep((new Random()).Next(0, 1000));  //WARN: Artificial latency here
            if (MyWorker.CancellationPending) { return; } //Bail out if MyWorker is cancelled
        }
    }

    public void CancelWorker() {
        if (MyWorker != null && MyWorker.IsBusy) {
            MyWorker.CancelAsync();
            System.Threading.ThreadStart WaitThread = new System.Threading.ThreadStart(delegate() {
                while (MyWorker.IsBusy) {
                    System.Threading.Thread.Sleep(100);
                }
            });
            WaitThread.BeginInvoke(a => {
                Invoke((MethodInvoker)delegate() { //Invoke your StuffAfterCancellation call back onto the UI thread
                    StuffAfterCancellation();
                });
            }, null);
        } else {
            StuffAfterCancellation();
        }
    }

    private void StuffAfterCancellation() {
        //Things to do after MyWorker is cancelled
    }
}

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

Infotekka
источник
0

Я знаю, что это действительно поздно (5 лет), но то, что вы ищете, - это использовать Thread и SynchronizationContext. . Вам придется маршалировать вызовы пользовательского интерфейса обратно в поток пользовательского интерфейса «вручную», а не позволять Framework делать это автоматически.

Это позволяет вам использовать поток, который вы можете ждать, если это необходимо.

Том Падилла
источник
0
Imports System.Net
Imports System.IO
Imports System.Text

Public Class Form1
   Dim f As New Windows.Forms.Form
  Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
   BackgroundWorker1.WorkerReportsProgress = True
    BackgroundWorker1.RunWorkerAsync()
    Dim l As New Label
    l.Text = "Please Wait"
    f.Controls.Add(l)
    l.Dock = DockStyle.Fill
    f.StartPosition = FormStartPosition.CenterScreen
    f.FormBorderStyle = Windows.Forms.FormBorderStyle.None
    While BackgroundWorker1.IsBusy
        f.ShowDialog()
    End While
End Sub




Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Dim i As Integer
    For i = 1 To 5
        Threading.Thread.Sleep(5000)
        BackgroundWorker1.ReportProgress((i / 5) * 100)
    Next
End Sub

Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    Me.Text = e.ProgressPercentage

End Sub

 Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

    f.Close()

End Sub

End Class
Nitesh
источник
Каков твой вопрос? В SO вы должны быть конкретными, задавая вопросы,
Алма До
@ Это ответ, а не вопрос.
Код L ver
0

Решение этой проблемы Фредриком Калсетом - лучшее, что я нашел до сих пор. Другие решения Application.DoEvent()могут вызывать проблемы или просто не работать. Позвольте мне преобразовать его решение в класс многократного использования. Поскольку BackgroundWorkerон не запечатан, мы можем унаследовать от него наш класс:

public class BackgroundWorkerEx : BackgroundWorker
{
    private AutoResetEvent _resetEvent = new AutoResetEvent(false);
    private bool _resetting, _started;
    private object _lockObject = new object();

    public void CancelSync()
    {
        bool doReset = false;
        lock (_lockObject) {
            if (_started && !_resetting) {
                _resetting = true;
                doReset = true;
            }
        }
        if (doReset) {
            CancelAsync();
            _resetEvent.WaitOne();
            lock (_lockObject) {
                _started = false;
                _resetting = false;
            }
        }
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        lock (_lockObject) {
            _resetting = false;
            _started = true;
            _resetEvent.Reset();
        }
        try {
            base.OnDoWork(e);
        } finally {
            _resetEvent.Set();
        }
    }
}

Используя флаги и правильную блокировку, мы гарантируем, что он _resetEvent.WaitOne()действительно будет вызван только в том случае, если какая-то работа была начата, иначе _resetEvent.Set();может никогда не быть вызвана!

Команда try-finally гарантирует, что _resetEvent.Set();она будет вызвана, даже если в нашем обработчике DoWork должно произойти исключение. Иначе приложение могло зависнуть при звонке навсегда CancelSync!

Мы бы использовали это так:

BackgroundWorkerEx _worker;

void StartWork()
{
    StopWork();
    _worker = new BackgroundWorkerEx { 
        WorkerSupportsCancellation = true,
        WorkerReportsProgress = true
    };
    _worker.DoWork += Worker_DoWork;
    _worker.ProgressChanged += Worker_ProgressChanged;
}

void StopWork()
{
    if (_worker != null) {
        _worker.CancelSync(); // Use our new method.
    }
}

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    for (int i = 1; i <= 20; i++) {
        if (worker.CancellationPending) {
            e.Cancel = true;
            break;
        } else {
            // Simulate a time consuming operation.
            System.Threading.Thread.Sleep(500);
            worker.ReportProgress(5 * i);
        }
    }
}

private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressLabel.Text = e.ProgressPercentage.ToString() + "%";
}

Вы также можете добавить обработчик к RunWorkerCompletedсобытию, как показано здесь:
     BackgroundWorker Class (документация Microsoft) .

Оливье Жако-Декомб
источник
0

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

Почему это так сложно?

Простой Thread.Sleep(1500)работает, но откладывает выключение (если слишком долго) или вызывает исключения (если слишком короткий).

Чтобы завершить работу сразу после завершения фонового рабочего, просто используйте переменную. Это работает для меня:

private volatile bool bwRunning = false;

...

private void MainWin_FormClosing(Object sender, FormClosingEventArgs e)
{
    ... // Clean house as-needed.

    bwInstance.CancelAsync();  // Flag background worker to stop.
    while (bwRunning)
        Thread.Sleep(100);  // Wait for background worker to stop.
}  // (The form really gets closed now.)

...

private void bwBody(object sender, DoWorkEventArgs e)
{
    bwRunning = true;

    BackgroundWorker bw = sender as BackgroundWorker;

    ... // Set up (open logfile, etc.)

    for (; ; )  // infinite loop
    {
        ...
        if (bw.CancellationPending) break;
        ...
    } 

    ... // Tear down (close logfile, etc.)

    bwRunning = false;
}  // (bwInstance dies now.)
A876
источник
0

Вы можете отказаться от события RunWorkerCompleted. Даже если вы уже добавили обработчик событий для _worker, вы можете добавить еще один, и они будут выполняться в том порядке, в котором они были добавлены.

public class DoesStuff
{
    BackgroundWorker _worker = new BackgroundWorker();

    ...

    public void CancelDoingStuff()
    {
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => 
        {
            // do whatever you want to do when the cancel completes in here!
        });
        _worker.CancelAsync();
    }
}

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

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if (_worker != null)
    {
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => this.Close());
        _worker.CancelAsync();
        e.Cancel = true;
    }
}
Шон Руби
источник
0

Я использую asyncметод и awaitжду, пока рабочий закончит свою работу:

    public async Task StopAsync()
    {
        _worker.CancelAsync();

        while (_isBusy)
            await Task.Delay(1);
    }

и в DoWorkметоде:

    public async Task DoWork()
    {
        _isBusy = true;
        while (!_worker.CancellationPending)
        {
            // Do something.
        }
        _isBusy = false;
    }

Вы также можете инкапсулировать whileпетлю DoWorkс , try ... catchчтобы установить _isBusyэто falseна исключение. Или просто проверить _worker.IsBusyвStopAsync цикле.

Вот пример полной реализации:

class MyBackgroundWorker
{
    private BackgroundWorker _worker;
    private bool _isBusy;

    public void Start()
    {
        if (_isBusy)
            throw new InvalidOperationException("Cannot start as a background worker is already running.");

        InitialiseWorker();
        _worker.RunWorkerAsync();
    }

    public async Task StopAsync()
    {
        if (!_isBusy)
            throw new InvalidOperationException("Cannot stop as there is no running background worker.");

        _worker.CancelAsync();

        while (_isBusy)
            await Task.Delay(1);

        _worker.Dispose();
    }

    private void InitialiseWorker()
    {
        _worker = new BackgroundWorker
        {
            WorkerSupportsCancellation = true
        };
        _worker.DoWork += WorkerDoWork;
    }

    private void WorkerDoWork(object sender, DoWorkEventArgs e)
    {
        _isBusy = true;
        try
        {
            while (!_worker.CancellationPending)
            {
                // Do something.
            }
        }
        catch
        {
            _isBusy = false;
            throw;
        }

        _isBusy = false;
    }
}

Чтобы остановить воркер и дождаться его завершения:

await myBackgroundWorker.StopAsync();

Проблемы с этим методом:

  1. Вы должны полностью использовать асинхронные методы.
  2. await Task.Delay неточно. На моем ПК Task.Delay (1) фактически ждет ~ 20 мс.
Энтони
источник
-2

о боже, некоторые из них стали до смешного сложными. все, что вам нужно сделать, это проверить свойство BackgroundWorker.CancellationPending внутри обработчика DoWork. Вы можете проверить это в любое время. как только он ожидается, установите e.Cancel = True и откажитесь от метода.

// метод здесь private void Worker_DoWork (отправитель объекта, DoWorkEventArgs e) {BackgroundWorker bw = (отправитель как BackgroundWorker);

// do stuff

if(bw.CancellationPending)
{
    e.Cancel = True;
    return;
}

// do other stuff

}


источник
1
И как с помощью этого решения человек, вызвавший CancelAsync, вынужден ждать отмены фонового рабочего?
Ян Бойд,