¿Cómo esperar a que se cancele un BackgroundWorker?


125

Considere un método hipotético de un objeto que hace cosas por usted:

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

    ...

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

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

¿Cómo se puede esperar a que se realice un BackgroundWorker?


En el pasado la gente ha intentado:

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

Pero esto se interrumpe , porque IsBusyno se borra hasta después de RunWorkerCompletedque se maneja el evento, y ese evento no se puede manejar hasta que la aplicación quede inactiva. La aplicación no estará inactiva hasta que el trabajador haya terminado. (Además, es un circuito ocupado, desagradable).

Otros han agregado sugerencias para incluirlo en:

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

El problema con esto es que Application.DoEvents()hace que se procesen los mensajes actualmente en la cola, lo que causa problemas de reincorporación (.NET no es reentrante).

Espero usar alguna solución que involucre objetos de sincronización de eventos, donde el código espera un evento, que los RunWorkerCompletedmanejadores de eventos del trabajador establecen. Algo como:

Event _workerDoneEvent = new WaitHandle();

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

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

Pero volví al punto muerto: el controlador de eventos no puede ejecutarse hasta que la aplicación esté inactiva, y la aplicación no estará inactiva porque está esperando un Evento.

Entonces, ¿cómo puedes esperar a que termine un BackgroundWorker?


Actualización La gente parece estar confundida por esta pregunta. Parecen pensar que usaré el BackgroundWorker como:

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

Es decir , no se, es decir , no lo que estoy haciendo, y que es no lo que se pide aquí. Si ese fuera el caso, no tendría sentido usar un trabajador de fondo.

Respuestas:


130

Si entiendo bien su requisito, podría hacer algo como esto (código no probado, pero muestra la idea general):

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
}

77
Esto bloqueará la interfaz de usuario (por ejemplo, sin repintar) si el trabajador en segundo plano tarda mucho en cancelar. Es mejor usar uno de los siguientes para detener la interacción con la interfaz de usuario: stackoverflow.com/questions/123661/…
Joe

1
+1 justo lo que recetó el médico ... aunque estoy de acuerdo con @Joe si la solicitud de cancelación puede demorar más de un segundo.
dotjoe el

¿Qué sucede cuando CancelAsync se maneja antes que WaitOne? ¿O es que el botón Cancelar solo funciona una vez?
CodingBarfield

66
Necesitaba comprobar el ((BackgroundWorker)sender).CancellationPendingpara obtener el evento de cancelación
Luuk

1
Como lo menciona Luuk, no es la propiedad Cancelar la que debe verificarse, sino la Cancelación pendiente. En segundo lugar, como menciona Joel Coehoorn, esperar que el hilo termine derrota el propósito de usar un hilo en primer lugar.
Capitán Sensible

15

Hay un problema con esta respuesta. La interfaz de usuario debe continuar procesando los mensajes mientras espera, de lo contrario no se volverá a pintar, lo que será un problema si su trabajador en segundo plano tarda mucho en responder a la solicitud de cancelación.

Una segunda falla es que _resetEvent.Set()nunca se llamará si el subproceso de trabajo produce una excepción, dejando que el subproceso principal espere indefinidamente, sin embargo, esta falla podría solucionarse fácilmente con un bloque try / finalmente.

Una forma de hacerlo es mostrar un cuadro de diálogo modal que tiene un temporizador que verifica repetidamente si el trabajador en segundo plano ha terminado el trabajo (o ha cancelado en su caso). Una vez que el trabajador en segundo plano ha finalizado, el cuadro de diálogo modal devuelve el control a su aplicación. El usuario no puede interactuar con la interfaz de usuario hasta que esto suceda.

Otro método (suponiendo que tenga un máximo de una ventana sin modo abierta) es establecer ActiveForm.Enabled = false, luego hacer un bucle en Application, DoEvents hasta que el trabajador en segundo plano haya terminado de cancelar, después de lo cual puede configurar ActiveForm.Enabled = true nuevamente.


55
Eso puede ser un problema, pero se acepta fundamentalmente como parte de la pregunta "Cómo esperar a que se cancele un BackgroundWorker". Esperar significa que esperas, no haces nada más. Esto también incluye el procesamiento de mensajes. Si no quisiera esperar al trabajador en segundo plano, podría llamar a .CancelAsync. Pero ese no es el requisito de diseño aquí.
Ian Boyd el

1
+1 para señalar el valor del método CancelAsync, versos que esperan al trabajador en segundo plano.
Ian Boyd el

10

Casi todos ustedes están confundidos por la pregunta y no entienden cómo se usa un trabajador.

Considere un controlador de eventos 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;
}

Y todo esta bien.

Ahora llega una situación en la que la persona que llama necesita abortar la cuenta regresiva porque necesita ejecutar una autodestrucción de emergencia del cohete.

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

    StartClaxon();
    SelfDestruct();
}

Y también hay una situación en la que necesitamos abrir las puertas de acceso al cohete, pero no mientras hacemos una cuenta regresiva:

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

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

Y, por último, necesitamos eliminar el combustible del cohete, pero eso no está permitido durante una cuenta regresiva:

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

    if (rocketOnPad)
        OpenFuelValves();
}

Sin la capacidad de esperar a que un trabajador cancele, debemos mover los tres métodos al 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();
}

Ahora podría escribir mi código así, pero no voy a hacerlo. No me importa, simplemente no lo soy.


18
¿Dónde está el método WaitForWorkerToFinish? cualquier código fuente completo?
Kiquenet

4

Puede registrarse en RunWorkerCompletedEventArgs en RunWorkerCompletedEventHandler para ver cuál era el estado. Éxito, cancelado o un error.

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

Actualización : para ver si su trabajador ha llamado .CancelAsync () usando esto:

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

2
El requisito es que CancelDoingStuff () no puede regresar hasta que se complete el trabajador. Comprobar cómo se completó realmente no es de interés.
Ian Boyd

Entonces tendrás que crear un evento. Realmente no tiene nada que ver con BackgroundWorker específicamente, solo necesita implementar un evento, escucharlo y disparar cuando haya terminado. Y RunWorkerCompletedEventHandler es cuando está hecho. Dispara otro evento.
Seb Nilsson el

4

Usted no espera a que el trabajador a fondo completa. Eso prácticamente anula el propósito de lanzar un hilo separado. En su lugar, debe dejar que termine su método y mover cualquier código que dependa de la finalización a un lugar diferente. Dejas que el trabajador te diga cuándo está hecho y luego llama a cualquier código restante.

Si desea esperar a que se complete algo, use una construcción de subprocesos diferente que proporcione un WaitHandle.


1
¿Puedes sugerir uno? Un trabajador de fondo parece ser la única construcción de subprocesos que puede enviar notificaciones al subproceso que construyó el objeto BackgroundWorker.
Ian Boyd

El trabajador de fondo es una construcción de interfaz de usuario . Simplemente usa eventos que su propio código aún necesita saber cómo llamar. Nada te impide crear tus propios delegados para eso.
Joel Coehoorn el

3

¿Por qué no puedes simplemente conectarte al evento BackgroundWorker.RunWorkerCompleted? Es una devolución de llamada que "ocurrirá cuando la operación en segundo plano se haya completado, se haya cancelado o se haya producido una excepción".


1
Porque la persona que usa el objeto DoesStuff le ha pedido que se cancele. Los recursos compartidos que usa el objeto están a punto de desconectarse, eliminarse, eliminarse y cerrarse, y necesita saber que está hecho para poder seguir adelante.
Ian Boyd

1

No entiendo por qué querrías esperar a que se complete un BackgroundWorker; realmente parece exactamente lo contrario de la motivación para la clase.

Sin embargo, puede iniciar cada método con una llamada al trabajador. IsBusy y hacer que salgan si se está ejecutando.


Mala elección de palabras; No estoy esperando que se complete.
Ian Boyd

1

Solo quiero decir que vine aquí porque necesito que un trabajador en segundo plano espere mientras ejecutaba un proceso asíncrono mientras estaba en un bucle, mi solución fue mucho más fácil que todas estas otras cosas ^^

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

Solo pensé en compartir porque aquí es donde terminé mientras buscaba una solución. Además, esta es mi primera publicación en el desbordamiento de pila, así que si es malo o algo así, ¡me encantarían las críticas! :)


0

Tal vez no estoy respondiendo bien tu pregunta.

El trabajador de fondo llama al evento WorkerCompleted una vez que su 'método de trabajo' (el método / función / sub que maneja el evento backgroundworker.doWork-event ) está terminado, por lo que no es necesario verificar si el BW aún se está ejecutando. Si desea detener a su trabajador, verifique la propiedad pendiente de cancelación dentro de su 'método de trabajador'.


Entiendo que el trabajador debe monitorear la propiedad pendiente de cancelación y salir lo antes posible. Pero, ¿cómo espera la persona en el exterior, que ha solicitado la cancelación del trabajador en segundo plano, que se haga?
Ian Boyd

El evento WorkCompleted seguirá disparándose.
Joel Coehoorn el

"¿Pero cómo la persona en el exterior, que ha solicitado la cancelación del trabajador de fondo, espera a que se haga?"
Ian Boyd

Espera a que se realice esperando que se active el evento WorkCompleted. Si desea evitar que el usuario haga clic en su GUI, puede usar una de las soluciones propuestas por @Joe en su respuesta anterior (deshabilite el formulario o muestre algo modal). En otras palabras, deje que el bucle inactivo del sistema lo espere; te dirá cuando haya terminado (disparando el evento).
Geoff

0

El flujo de trabajo de un BackgroundWorkerobjeto básicamente requiere que maneje el RunWorkerCompletedevento tanto para la ejecución normal como para los casos de uso de cancelación del usuario. Esta es la razón por la cual existe la propiedad RunWorkerCompletedEventArgs.Cancelled . Básicamente, hacer esto correctamente requiere que consideres que tu método Cancel es un método asincrónico en sí mismo.

Aquí hay un ejemplo:

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

Si realmente no desea que su método salga, le sugiero que coloque una bandera como una AutoResetEventen un derivado BackgroundWorker, luego anule OnRunWorkerCompletedpara establecer la bandera. Sin embargo, todavía es un poco torpe; Recomiendo tratar el evento cancel como un método asincrónico y hacer lo que esté haciendo actualmente en el RunWorkerCompletedcontrolador.


Mover código a RunWorkerCompleted no es donde pertenece, y no es bonito.
Ian Boyd

0

Llego un poco tarde a la fiesta aquí (aproximadamente 4 años), pero ¿qué pasa con la configuración de un subproceso asincrónico que puede manejar un bucle ocupado sin bloquear la interfaz de usuario, luego hacer que la devolución de llamada de ese subproceso sea la confirmación de que BackgroundWorker ha terminado de cancelar ?

Algo como esto:

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

En esencia, lo que esto hace es disparar otro hilo para ejecutarse en segundo plano que solo espera en su bucle ocupado para ver si se MyWorkerha completado. Una vez que MyWorkerhaya finalizado la cancelación, el subproceso saldrá y podemos usarlo AsyncCallbackpara ejecutar cualquier método que necesitemos para seguir la cancelación exitosa: funcionará como un evento psuedo. Dado que esto está separado del hilo de la interfaz de usuario, no bloqueará la interfaz de usuario mientras esperamos a MyWorkerque finalice la cancelación. Si su intención realmente es bloquear y esperar la cancelación, entonces esto es inútil para usted, pero si solo desea esperar para poder comenzar otro proceso, entonces esto funciona muy bien.


0

Sé que esto es realmente tarde (5 años), pero lo que está buscando es usar un hilo y un contexto de sincronización . Tendrá que ordenar las llamadas de la interfaz de usuario al hilo de la interfaz de usuario "a mano" en lugar de dejar que el Marco lo haga automáticamente.

Esto le permite usar un hilo que puede esperar si es necesario.


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

¿Cuál es tu pregunta? En SO tienes que ser específico al hacer preguntas
Alma Do

@Esta es la respuesta, no una pregunta.
Código L ღ ver

0

La solución de Fredrik Kalseth a este problema es la mejor que he encontrado hasta ahora. Se utilizan otras soluciones Application.DoEvent()que pueden causar problemas o simplemente no funcionan. Déjame convertir su solución en una clase reutilizable. Como BackgroundWorkerno está sellado, podemos derivar nuestra clase de él:

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 banderas y un bloqueo adecuado, nos aseguramos de que _resetEvent.WaitOne()realmente solo se llame si se ha iniciado algún trabajo, de lo contrario, ¡ _resetEvent.Set();tal vez nunca se llame!

El try-finally asegura que _resetEvent.Set();se llamará, incluso si se produce una excepción en nuestro controlador DoWork. De lo contrario, la aplicación podría congelarse para siempre al llamar CancelSync.

Lo usaríamos así:

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

También puede agregar un controlador al RunWorkerCompletedevento como se muestra aquí:
     Clase BackgroundWorker (documentación de Microsoft) .


0

Cerrar el formulario cierra mi archivo de registro abierto. Mi trabajador en segundo plano escribe ese archivo de registro, por lo que no puedo dejar de MainWin_FormClosing()terminar hasta que termine mi trabajador en segundo plano. Si no espero a que termine mi trabajador en segundo plano, se producen excepciones.

¿Por qué es tan difícil?

Un simple Thread.Sleep(1500)funciona, pero retrasa el apagado (si es demasiado largo) o causa excepciones (si es demasiado corto).

Para cerrar justo después de que termine el trabajador en segundo plano, simplemente use una variable. Esto es trabajo para mí:

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

Puede recuperar el evento RunWorkerCompleted. Incluso si ya ha agregado un controlador de eventos para _worker, puede agregar otro y ejecutarán en el orden en que se agregaron.

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

Esto podría ser útil si tiene varias razones por las que puede ocurrir una cancelación, lo que hace que la lógica de un solo controlador RunWorkerCompleted sea más complicada de lo que desea. Por ejemplo, cancelar cuando un usuario intenta cerrar el formulario:

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 el asyncmétodo y awaitespero a que el trabajador termine su trabajo:

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

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

y en DoWorkmétodo:

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

También puede encapsular el whilebucle DoWorkcon try ... catchpara establecer _isBusyes una falseexcepción. O simplemente verifique _worker.IsBusyel StopAsyncciclo while.

Aquí hay un ejemplo de implementación completa:

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

Para detener al trabajador y esperar a que se ejecute hasta el final:

await myBackgroundWorker.StopAsync();

Los problemas con este método son:

  1. Tienes que usar métodos asíncronos todo el tiempo.
  2. esperar Tarea. El retraso es inexacto. En mi PC, Task.Delay (1) en realidad espera ~ 20 ms.

-2

oh hombre, algunos de estos se han vuelto ridículamente complejos. todo lo que necesita hacer es verificar la propiedad BackgroundWorker.CancellationPending dentro del controlador DoWork. Puedes consultarlo en cualquier momento. una vez que esté pendiente, establezca e.Cancel = True y salga del método.

// método aquí privado void Worker_DoWork (remitente del objeto, DoWorkEventArgs e) {BackgroundWorker bw = (remitente como BackgroundWorker);

// do stuff

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

// do other stuff

}


1
Y con esta solución, ¿cómo se ve obligada la persona que llamó a CancelAsync a esperar a que se cancele el trabajador en segundo plano?
Ian Boyd
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.