Вопрос:

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

c# .net multithreading backgroundworker

74307 просмотра

18 ответа

122047 Репутация автора

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

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);

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

Автор: Ian Boyd Источник Размещён: 23.09.2008 08:30

Ответы (18)


4 плюса

14074 Репутация автора

Вы можете проверить 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");
}
Автор: Seb Nilsson Размещён: 23.09.2008 08:36

3 плюса

2674 Репутация автора

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

Автор: Rick Minerich Размещён: 23.09.2008 08:39

1 плюс

40270 Репутация автора

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

Тем не менее, вы можете запустить каждый метод с вызовом worker.IsBusy и заставить его выйти, если он запущен.

Автор: Austin Salonen Размещён: 23.09.2008 08:41

0 плюса

729 Репутация автора

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

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

Автор: Stephan Размещён: 23.09.2008 08:42

4 плюса

311947 Репутация автора

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

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

Автор: Joel Coehoorn Размещён: 23.09.2008 08:43

121 плюса

11035 Репутация автора

Решение

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

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
}
Автор: Fredrik Kalseth Размещён: 23.09.2008 08:52

0 плюса

17795 Репутация автора

Рабочий процесс 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 Размещён: 23.09.2008 09:02

13 плюса

101212 Репутация автора

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

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

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

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

Автор: Joe Размещён: 24.09.2008 11:22

10 плюса

122047 Репутация автора

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

Рассмотрим обработчик события 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();
}

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

Автор: Ian Boyd Размещён: 24.09.2008 02:09

-2 плюса

0 Репутация автора

о человек, некоторые из них стали смехотворно сложными. все, что вам нужно сделать, это проверить свойство 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

}

Автор: eric burke Размещён: 24.09.2008 07:57

0 плюса

8735 Репутация автора

Я немного опоздал на вечеринку здесь (около 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 Размещён: 06.07.2012 11:08

0 плюса

1 Репутация автора

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 Размещён: 28.08.2013 10:18

0 плюса

340 Репутация автора

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

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

Автор: Tom Padilla Размещён: 11.10.2013 08:06

0 плюса

69318 Репутация автора

Решение Фредрика Калсета этой проблемы - лучшее, что я нашел до сих пор. Используются другие решения, 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) .

Автор: Olivier Jacot-Descombes Размещён: 27.06.2014 05:54

1 плюс

141 Репутация автора

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

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

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

Автор: Connor Williams Размещён: 18.06.2015 04:30

0 плюса

137 Репутация автора

Закрытие формы закрывает мой открытый лог-файл. Мой фоновый работник записывает этот файл журнала, поэтому я не могу 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 Размещён: 28.04.2017 11:17

0 плюса

65 Репутация автора

Вы можете отказаться от события 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;
    }
}
Автор: Shawn Rubie Размещён: 27.11.2017 10:56

0 плюса

1582 Репутация автора

Я использую 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 мс.
Автор: Anthony Размещён: 29.03.2019 10:11
32x32