2008-09-23 3 views
110

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

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

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

Другие добавить предложил kludging его в:

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?


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

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

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

ответ

116

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

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

Это отлично работает, за исключением одной ошибки. Событие необходимо создать в состоянии «не установлено»: частный AutoResetEvent _resetEvent = новый AutoResetEvent (false); – 2008-09-23 21:17:12

4

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

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

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

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

Требование о том, что CancelDoingStuff() не может вернуться до завершения работника. Проверка того, как она завершена, действительно не представляет интереса. – 2008-09-23 20:38:32

+0

Тогда вам нужно будет создать событие. Это действительно не имеет ничего общего с BackgroundWorker, вам просто нужно реализовать событие, послушать его и запустить, когда это будет сделано. И RunWorkerCompletedEventHandler - это когда это делается. Опорожните другое событие. – 2008-09-23 21:03:01

3

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

+0

Потому что человек, который использует объект DoesStuff, попросил его отменить. Общие ресурсы, которые использует объект, собираются отключить, удалить, удалить, закрыть, и он должен знать, что это сделано, чтобы я мог продолжить. – 2008-09-23 20:47:55

1

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

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

+0

Плохой выбор слов; Я не дожидаюсь его завершения. – 2008-09-24 13:44:58

0

Хм, может быть, я не получаю свой вопрос прямо.

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

+0

Я понимаю, что работник должен контролировать свойство CancellationPending и выходить как можно скорее. Но как нарушителя, который попросил фонового работника отменить, ждать, пока он будет выполнен? – 2008-09-23 20:52:44

+0

Событие WorkCompleted по-прежнему срабатывает. – 2008-09-23 20:58:26

+0

«Но как нарушитель, который попросил фонового рабочего отменить, ждать, пока это будет сделано?» – 2008-09-24 13:45:55

4

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

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

+1

Можете ли вы предложить его? Фоновый работник, похоже, является единственной конструкцией потоков, которая может отправлять уведомления потоку, который создал объект BackgroundWorker. – 2008-09-23 21:01:10

+0

Фоновый работник - это конструкция _UI_. Он просто использует события, которые ваш собственный код все еще должен знать, как звонить. Ничто не мешает вам создавать для этого своих собственных делегатов. – 2008-09-23 22:00:27

0

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

Вот пример:

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 установить флаг. Тем не менее, это еще что-то вроде kludgy; Я бы рекомендовал рассматривать событие отмены как асинхронный метод и делать то, что он делает в обработчике RunWorkerCompleted.

13

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

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

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

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

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

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

-2

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

метод // здесь частная пустота Worker_DoWork (объект отправителя, DoWorkEventArgs е) { BackgroundWorker мт = (отправитель, как BackgroundWorker);

// do stuff 

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

// do other stuff 

}

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

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

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

0

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

1

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

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

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

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((sender3, e3) => this.Close()); 
     _worker.CancelAsync(); 
     e.Cancel = true; 
    } 
} 
Смежные вопросы