Come attendere l'annullamento di un BackgroundWorker?


125

Considera un metodo ipotetico di un oggetto che fa cose per te:

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

    ...

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

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

Come si può attendere l'esecuzione di un BackgroundWorker?


In passato le persone hanno provato:

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

Ma questo deadlock si blocca perché IsBusynon viene cancellato fino a quando l' RunWorkerCompletedevento non viene gestito e quell'evento non può essere gestito fino a quando l'applicazione diventa inattiva. L'applicazione non diventerà inattiva fino a quando il lavoratore non avrà terminato. (Inoltre, è un circuito affollato - disgustoso.)

Altri hanno aggiunto suggerendo di includerlo in:

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

Il problema è che i Application.DoEvents()messaggi attualmente in coda vengono elaborati, causando problemi di rientro (.NET non rientra).

Spero di utilizzare una soluzione che coinvolga gli oggetti di sincronizzazione degli eventi, in cui il codice attende un evento, RunWorkerCompletedimpostato dai gestori eventi del lavoratore . Qualcosa di simile a:

Event _workerDoneEvent = new WaitHandle();

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

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

Ma sono tornato al punto morto: il gestore di eventi non può essere eseguito fino a quando l'applicazione diventa inattiva e l'applicazione non sarà inattiva perché sta aspettando un evento.

Quindi, come puoi aspettare che un BackgroundWorker finisca?


Aggiornamento Le persone sembrano essere confuse da questa domanda. Sembrano pensare che userò BackgroundWorker come:

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

Cioè , non è, cioè non quello che sto facendo, e questo è non ciò che viene chiesto qui. Se così fosse, non avrebbe senso utilizzare un lavoratore in background.

Risposte:


130

Se capisco bene le tue esigenze, potresti fare qualcosa del genere (codice non testato, ma mostra l'idea generale):

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
Ciò bloccherà l'interfaccia utente (ad es. Nessuna ridistribuzione) se il lavoratore in background impiega molto tempo per annullare. È meglio utilizzare una delle seguenti opzioni per interrompere l'interazione con l'interfaccia utente: stackoverflow.com/questions/123661/…
Joe

1
+1 proprio quello che il medico ha ordinato ... anche se sono d'accordo con @Joe se la richiesta di annullamento può richiedere più di un secondo.
dotjoe,

Cosa succede quando CancelAsync viene gestito prima di WaitOne? O è il pulsante Annulla funziona solo una volta.
CodingBarfield

6
Ho bisogno di controllare il ((BackgroundWorker)sender).CancellationPendingper ottenere l'evento di annullamento
Luuk

1
Come menzionato da Luuk, non è la proprietà Annulla che deve essere verificata, ma CancelPending. In secondo luogo, come menziona Joel Coehoorn, attendere che il thread si interrompa sconfigge lo scopo di utilizzare un thread in primo luogo.
Captain Sensible,

15

C'è un problema con questo risposta. L'interfaccia utente deve continuare a elaborare i messaggi durante l'attesa, altrimenti non verrà ridipinta, il che sarà un problema se il lavoratore in background impiega molto tempo a rispondere alla richiesta di annullamento.

Un secondo difetto è quello _resetEvent.Set() non verrà mai chiamato se il thread di lavoro genera un'eccezione - lasciando il thread principale in attesa indefinitamente - tuttavia questo difetto potrebbe essere facilmente risolto con un blocco try / finally.

Un modo per farlo è quello di visualizzare una finestra di dialogo modale che ha un timer che controlla ripetutamente se il lavoratore in background ha terminato il lavoro (o ha terminato l'annullamento nel tuo caso). Una volta terminato il lavoratore in background, la finestra di dialogo modale restituisce il controllo all'applicazione. L'utente non può interagire con l'interfaccia utente fino a quando ciò non accade.

Un altro metodo (supponendo che sia aperto un massimo di una finestra non modale) è impostare ActiveForm.Enabled = false, quindi eseguire il ciclo su Applicazione, DoEvents fino a quando il lavoratore in background ha terminato l'annullamento, dopodiché è possibile impostare nuovamente ActiveForm.Enabled = true.


5
Questo può essere un problema, ma è fondamentalmente accettato come parte della domanda "Come attendere l' annullamento di un BackgroundWorker". Aspettare significa aspettare, non fare nient'altro. Ciò include anche l'elaborazione dei messaggi. Se non volessi aspettare il lavoratore in background potresti semplicemente chiamare .CancelAsync. Ma questo non è il requisito di progettazione qui.
Ian Boyd,

1
+1 per indicare il valore del metodo CancelAsync, versetti in attesa del lavoratore in background.
Ian Boyd,

10

Quasi tutti voi siete confusi dalla domanda e non capite come viene utilizzato un lavoratore.

Prendi in considerazione un gestore eventi 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;
}

E va tutto bene.

Ora arriva una situazione in cui il chiamante deve interrompere il conto alla rovescia perché deve eseguire un'autodistruzione di emergenza del razzo.

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

    StartClaxon();
    SelfDestruct();
}

E c'è anche una situazione in cui dobbiamo aprire le porte di accesso al razzo, ma non mentre facciamo un conto alla rovescia:

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

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

E infine, dobbiamo scaricare il razzo, ma ciò non è consentito durante un conto alla rovescia:

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

    if (rocketOnPad)
        OpenFuelValves();
}

Senza la possibilità di attendere l'annullamento di un lavoratore, è necessario spostare tutti e tre i metodi in 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();
}

Ora potrei scrivere il mio codice in questo modo, ma non lo farò. Non mi interessa, non lo sono.


18
Dov'è il metodo WaitForWorkerToFinish? qualche codice sorgente completo?
Kiquenet,

4

È possibile verificare in RunWorkerCompletedEventArgs in RunWorkerCompletedEventHandler per vedere quale fosse lo stato. Esito positivo, annullato o errore.

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

Aggiornamento : per vedere se il tuo lavoratore ha chiamato .CancelAsync () usando questo:

if (_worker.CancellationPending)
{
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}

2
Il requisito è che CancelDoingStuff () non possa tornare fino al completamento del lavoratore. Controllare come è stato completato non è davvero interessante.
Ian Boyd,

Quindi dovrai creare un evento. In realtà non ha nulla a che fare con BackgroundWorker in particolare, devi solo implementare un evento, ascoltarlo e sparare quando hai finito. E RunWorkerCompletedEventHandler è quando ha finito. Spara un altro evento.
Seb Nilsson,

4

Lei non attendere il rilascio del fondo lavoratore completa. Questo praticamente vanifica lo scopo di lanciare un thread separato. Invece, dovresti completare il tuo metodo e spostare qualsiasi codice che dipende dal completamento in un altro posto. Lascia che il lavoratore ti dica quando ha finito e chiama qualsiasi codice rimanente.

Se si desidera attendere il completamento di qualcosa, utilizzare un costrutto di threading diverso che fornisca WaitHandle.


1
Puoi suggerirne uno? Un lavoratore in background sembra essere l'unico costrutto di threading in grado di inviare notifiche al thread che ha creato l'oggetto BackgroundWorker.
Ian Boyd,

Il backgroundworker è un costrutto UI . Usa solo un evento che il tuo codice deve ancora sapere come chiamare. Nulla ti impedisce di creare i tuoi delegati per questo.
Joel Coehoorn,

3

Perché non puoi semplicemente legarti all'evento BackgroundWorker.RunWorkerCompleted. È un callback che si verifica quando l'operazione in background è stata completata, è stata annullata o ha sollevato un'eccezione.


1
Perché la persona che sta utilizzando l'oggetto DoesStuff gli ha chiesto di annullare. Le risorse condivise utilizzate dall'oggetto stanno per essere disconnesse, rimosse, eliminate, chiuse e deve sapere che è stato fatto per poter procedere.
Ian Boyd,

1

Non capisco perché vorresti aspettare il completamento di un BackgroundWorker; sembra davvero l'esatto contrario della motivazione per la classe.

Tuttavia, è possibile avviare tutti i metodi con una chiamata a worker.IsBusy e farli uscire se è in esecuzione.


Cattiva scelta delle parole; non aspetto il completamento.
Ian Boyd,

1

Voglio solo dire che sono venuto qui perché ho bisogno di un lavoratore in background per aspettare mentre stavo eseguendo un processo asincrono mentre in un ciclo, la mia correzione era molto più semplice di tutte queste altre cose ^^

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

Ho solo pensato di condividere perché è qui che sono finito mentre cercavo una soluzione. Inoltre, questo è il mio primo post su overflow dello stack, quindi se è cattivo o qualcosa che amerei i critici! :)


0

Forse non sto capendo bene la tua domanda.

Il lavoratore in background chiama l'evento WorkerCompleted una volta terminato il suo "metodo di lavoro" (il metodo / funzione / sub che gestisce l' evento in backgroundworker.doWork ), quindi non è necessario verificare se il BW è ancora in esecuzione. Se vuoi fermare il tuo lavoratore controlla la proprietà in attesa di annullamento all'interno del tuo "metodo lavoratore".


Comprendo che il lavoratore deve monitorare la proprietà CancelPending ed uscire il prima possibile. Ma come fa la persona esterna, che ha richiesto il lavoratore in background, ad attendere che venga eseguita?
Ian Boyd,

L'evento WorkCompleted verrà comunque generato.
Joel Coehoorn,

"Ma come fa la persona all'esterno, che ha richiesto il lavoratore in background, ad aspettare che sia fatto?"
Ian Boyd,

Aspetti che venga fatto aspettando che l'evento WorkCompleted si attivi. Se si desidera impedire all'utente di fare in modo che un clic si adatti a tutta la GUI, è possibile utilizzare una delle soluzioni proposte da @Joe nella sua risposta sopra (disabilitare il modulo o mostrare qualcosa di modale). In altre parole, lascia che il loop inattivo del sistema ti faccia aspettare; ti dirà quando è terminato (lanciando l'evento).
Geoff

0

Il flusso di lavoro di un BackgroundWorkeroggetto richiede fondamentalmente la gestione RunWorkerCompleteddell'evento per casi d'uso sia di normale esecuzione sia di cancellazione dell'utente. Questo è il motivo per cui la proprietà RunWorkerCompletedEventArgs.Cancelled esiste . Fondamentalmente, farlo correttamente richiede di considerare il metodo Cancel come un metodo asincrono in sé.

Ecco un esempio:

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

Se davvero non vuoi che il tuo metodo esca, ti suggerirei di mettere una bandiera come AutoResetEventsu un derivato BackgroundWorker, quindi eseguire OnRunWorkerCompletedl' override per impostare la bandiera. È comunque un po 'kludgy; Consiglio di trattare l'evento di annullamento come un metodo asincrono e di fare qualunque cosa stia facendo attualmente nel RunWorkerCompletedgestore.


Lo spostamento del codice in RunWorkerCompleted non è il posto a cui appartiene e non è carino.
Ian Boyd,

0

Sono un po 'in ritardo alla festa qui (circa 4 anni) ma per quanto riguarda l'impostazione di un thread asincrono in grado di gestire un loop occupato senza bloccare l'interfaccia utente, quindi avere il callback da quel thread essere la conferma che BackgroundWorker ha terminato l'annullamento ?

Qualcosa come questo:

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
    }
}

In sostanza, ciò che fa è lanciare un altro thread da eseguire in background che attende solo nel suo ciclo occupato per vedere se MyWorkerè stato completato. Una volta MyWorkerterminato l'annullamento, il thread verrà chiuso e possiamo usarlo AsyncCallbackper eseguire qualsiasi metodo di cui abbiamo bisogno per seguire l'annullamento riuscito - funzionerà come un evento psuedo. Poiché questo è separato dal thread dell'interfaccia utente, non bloccherà l'interfaccia utente mentre aspettiamo MyWorkerdi terminare l'annullamento. Se la tua intenzione è davvero quella di bloccare e attendere l'annullamento, questo è inutile per te, ma se vuoi solo aspettare in modo da poter avviare un altro processo, allora funziona bene.


0

So che è molto tardi (5 anni) ma quello che stai cercando è usare un thread e un SynchronizationContext . Dovrai eseguire il marshalling delle chiamate dell'interfaccia utente al thread dell'interfaccia utente "a mano" piuttosto che lasciare che il Framework lo faccia automaticamente.

Ciò ti consente di utilizzare un thread che puoi attendere se necessario.


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

Qual'è la tua domanda? In SO devi essere specifico quando
fai

@Questa è la risposta non una domanda.
Codice L ღ ver

0

La soluzione di Fredrik Kalseth a questo problema è la migliore che ho trovato finora. Altre soluzioni utilizzano Application.DoEvent()che possono causare problemi o semplicemente non funzionare. Consentitemi di gettare la sua soluzione in una classe riutilizzabile. Poiché BackgroundWorkernon è sigillato, possiamo derivarne la nostra classe:

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

Con le bandiere e il blocco corretto, ci assicuriamo che _resetEvent.WaitOne()venga chiamato davvero solo se è stato avviato un lavoro, altrimenti _resetEvent.Set();non potrebbe mai essere chiamato!

Try-finally garantisce che _resetEvent.Set();verrà chiamato, anche se nel nostro gestore DoWork dovesse verificarsi un'eccezione. Altrimenti l'applicazione potrebbe bloccarsi per sempre durante la chiamataCancelSync !

Lo useremmo così:

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() + "%";
}

È inoltre possibile aggiungere un gestore RunWorkerCompletedall'evento, come mostrato qui:
     BackgroundWorker Class (documentazione Microsoft) .


0

La chiusura del modulo chiude il mio file di registro aperto. Il mio lavoratore in background scrive quel file di registro, quindi non posso lasciare che MainWin_FormClosing()finisca il mio lavoratore in background. Se non aspetto il termine del mio lavoratore in background, si verificano eccezioni.

Perche'e'cosi difficile?

Un semplice Thread.Sleep(1500)funziona, ma ritarda l'arresto (se troppo lungo) o provoca eccezioni (se troppo breve).

Per chiudere subito dopo il termine del lavoratore in background, basta usare una variabile. Questo funziona per me:

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

È possibile tornare indietro dall'evento RunWorkerCompleted. Anche se hai già aggiunto un gestore eventi per _worker, puoi aggiungerne un altro che eseguiranno nell'ordine in cui sono stati aggiunti.

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

ciò potrebbe essere utile se si hanno più ragioni per cui può verificarsi un annullamento, rendendo la logica di un singolo gestore RunWorkerCompleted più complicata di quanto si desideri. Ad esempio, annullando quando un utente tenta di chiudere il modulo:

void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if (_worker != null)
    {
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => this.Close());
        _worker.CancelAsync();
        e.Cancel = true;
    }
}

0

Uso il asyncmetodo e awaitaspetto che il lavoratore finisca il suo lavoro:

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

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

e nel DoWorkmetodo:

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

È inoltre possibile incapsulare il whileciclo DoWorkcon try ... catchper impostare _isBusyè falsein eccezione. Oppure, controlla semplicemente _worker.IsBusynel StopAsyncciclo while.

Ecco un esempio di piena attuazione:

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

Per interrompere il lavoratore e attendere fino alla fine:

await myBackgroundWorker.StopAsync();

I problemi con questo metodo sono:

  1. Devi usare i metodi asincroni fino in fondo.
  2. waitit Task.Delay è impreciso. Sul mio PC, Task.Delay (1) in realtà attende ~ 20ms.

-2

oh amico, alcuni di questi sono diventati ridicolmente complessi. tutto quello che devi fare è controllare la proprietà BackgroundWorker.CancellationPending all'interno del gestore DoWork. puoi verificarlo in qualsiasi momento. una volta che è in sospeso, imposta e.Cancel = True e salvi dal metodo.

// metodo qui privato void Worker_DoWork (mittente oggetto, DoWorkEventArgs e) {BackgroundWorker bw = (mittente come BackgroundWorker);

// do stuff

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

// do other stuff

}


1
E con questa soluzione in che modo la persona che ha chiamato CancelAsync è costretta ad attendere l'annullamento del lavoratore in background?
Ian Boyd,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.