Як чекати, коли 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 is re-entrant).

Я би сподівався використати якесь рішення, що стосується об'єктів синхронізації подій, де код чекає події, яку 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
Як зазначав Луук, це не має бути перевірено властивість «Скасувати», а «Скасувати». По-друге, як згадує Джоель Куехорн, очікування того, що нитка припиниться, перемагає мета використання нитки в першу чергу.
Капітан Чутливий

15

У цій відповіді є проблема . Користувальницький інтерфейс повинен продовжувати обробляти повідомлення, поки ви чекаєте, інакше він не перефарбується, що буде проблемою, якщо ваш фоновий працівник потребує тривалого часу, щоб відповісти на запит на скасування.

Другий недолік, який _resetEvent.Set()ніколи не буде викликаний, якщо робоча нитка кидає виняток - залишаючи основну нитку в очікуванні нескінченно - однак цей недолік можна було легко виправити за допомогою блоку спробу / нарешті.

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

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


5
Це може бути проблемою, але вона принципово прийнята як частина питання "Як чекати, коли BackgroundWorker скасує". Очікування означає, що ви чекаєте, ви більше нічого не робите. Сюди також відноситься обробка повідомлень. Якщо я не хотів чекати фонового працівника, ви можете просто зателефонувати .CancelAsync. Але це не вимога дизайну тут.
Ян Бойд

1
+1 для вказівки значення методу CancelAsync, віршів, які очікують фонового працівника.
Ян Бойд

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? будь-який повний вихідний код?
Кікенет

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.
Ян Бойд

Фоновий працівник - це конструкція інтерфейсу користувача . Він просто використовує події, до яких ваш власний код ще повинен знати, як зателефонувати. Ніщо не заважає вам створити для цього власних делегатів.
Joel Coehoorn

3

Чому ви не можете просто прив’язатись до події BackgroundWorker.RunWorkerCompleted. Це зворотний виклик, який "відбудеться, коли фонова операція завершена, скасована або порушена виняток".


1
Тому що людина, яка використовує об’єкт DoesStuff, попросила його скасувати. Спільні ресурси, якими користується об’єкт, збираються відключити, вилучити, утилізувати, закрити, і потрібно знати, що це зроблено, щоб я міг іти вперед.
Ян Бойд

1

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

Однак ви можете запустити кожен метод із виклику до Working.IsBusy і дозволити їм вийти, якщо він працює.


Поганий вибір слів; Я не чекаю його завершення.
Ян Бойд

1

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

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

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


0

Гм, можливо, я не розумію вашого питання правильно.

Фоновий працівник викликає подію WorkerCompleted, як тільки його 'radmethod' (метод / функція / підрозділ, який обробляє події backgroundworker.doWork ), закінчується, тому немає необхідності перевіряти, чи BW все ще працює. Якщо ви хочете зупинити свого працівника, перевірте майно, що очікує на скасування, всередині вашого «методу працівника».


я розумію, що працівник повинен стежити за властивістю CancellationPending та виходити якомога швидше. Але як людина з зовнішньої сторони, яка попросила звільнити працівника, чекає, коли це буде зроблено?
Ян Бойд

Подія WorkComplete завершиться.
Joel Coehoorn

"Але як людина з зовнішньої сторони, яка попросила звільнити працівника, чекає, коли це буде зроблено?"
Ян Бойд

Ви чекаєте, коли це буде зроблено, дочекавшись запуску події WorkCompleted. Якщо ви хочете не допустити, щоб користувач міг натиснути по всій графічному інтерфейсу, ви можете скористатися одним із рішень, запропонованих @Joe у своїй відповіді вище (відключити форму або показати щось модальне). Іншими словами, нехай цикл холостого ходу системи виконує очікування на вас; він скаже вам, коли він закінчиться (розпалюючи подію).
Джефф

0

В BackgroundWorkerосновному робочий процес об'єкта вимагає, щоб ви обробляли RunWorkerCompletedподію як для звичайного виконання, так і для випадків використання скасування користувача. Ось чому властивість RunWorkerCompletedEventArgs.Canceiled існує. В основному, для цього правильно потрібно, щоб ви вважали ваш метод Скасування асинхронним методом сам по собі.

Ось приклад:

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обробнику.


Переміщення коду до 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для виконання будь-якого методу, необхідного для наслідування успішного скасування - він буде працювати як подію psuedo. Оскільки це окремо від потоку інтерфейсу, він не заблокує користувальницький інтерфейс, поки ми будемо чекати MyWorkerзавершення скасування. Якщо ви дійсно маєте намір заблокувати та чекати скасування, то це вам марно, але якщо ви просто хочете почекати, щоб ви могли розпочати інший процес, тоді це прекрасно працює.


0

Я знаю, що це справді пізно (5 років), але те, що ви шукаєте, - це використовувати Thread and 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

Яке ваше запитання? У ТА ви маєте бути конкретними, коли ставите запитання
Alma Do

@ Це відповідь не на запитання.
Code 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();ніколи не може бути викликана!

Спроба остаточно гарантує, що _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 (документація 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.)

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. зачекайте 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, змушена чекати, коли фоновий працівник скасує?
Ян Бойд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.