BackgroundWorkerがキャンセルするのを待つ方法は?


125

あなたのために何かをするオブジェクトの架空の方法を考えてみましょう:

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

    ...

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

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

どのようにBackgroundWorkerが完了するのを待つことができますか?


過去に人々は試みました:

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

しかし、このデッドロック、理由はIsBusy後になるまでクリアされないRunWorkerCompletedイベントが処理され、アプリケーションがアイドル状態になるまでそのイベントが処理取得することはできません。ワーカーが完了するまで、アプリケーションはアイドル状態になりません。(さらに、それは忙しいループです-嫌です)。

他の人はそれを提案する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 Peopleはこの質問に混乱しているようです。彼らは私がBackgroundWorkerを次のように使用することを考えているようです:

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

それはあるではないですもの、それはない私がやっている、それはありません、ここで求められているもの。その場合、バックグラウンドワーカーを使用しても意味がありません。

回答:


130

私があなたの要件を正しく理解しているなら、あなたはこのようなことをすることができます(コードはテストされていませんが、一般的な考えを示しています):

private BackgroundWorker worker = new BackgroundWorker();
private AutoResetEvent _resetEvent = new AutoResetEvent(false);

public Form1()
{
    InitializeComponent();

    worker.DoWork += worker_DoWork;
}

public void Cancel()
{
    worker.CancelAsync();
    _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    while(!e.Cancel)
    {
        // do something
    }

    _resetEvent.Set(); // signal that worker is done
}

7
これにより、バックグラウンドワーカーのキャンセルに長い時間がかかる場合、UIがブロックされます(たとえば、再描画されません)。これは、UIと停止の相互作用に、次のいずれかを使用することをお勧めします:stackoverflow.com/questions/123661/...
ジョー・

1
+1医師が注文したものに...キャンセル要求が1秒以上かかる場合は、@ Joeに同意します。
dotjoe 2009

CancelAsyncがWaitOneの前に処理されるとどうなりますか?または、[キャンセル]ボタンは1回だけ機能しますか。
CodingBarfield、2011

6
((BackgroundWorker)sender).CancellationPendingキャンセルイベントを取得するにはを確認する必要がありました
Luuk

1
Luukが述べたように、チェックする必要があるのはCancelプロパティではなく、CancellationPendingです。次に、Joel Coehoornが述べているように、スレッドが終了するのを待つことは、そもそもスレッドを使用する目的を無効にします。
キャプテンセンシブル

15

この応答に問題があります。UIは待機中もメッセージを処理し続ける必要があります。そうしないと再描画されません。これは、バックグラウンドワーカーがキャンセルリクエストに応答するのに長い時間がかかる場合に問題になります。

2番目の欠陥は_resetEvent.Set()、ワーカースレッドが例外をスローした場合に呼び出されないことです。メインスレッドが無期限に待機したままですが、この欠陥は、try / finallyブロックで簡単に修正できます。

これを行う1つの方法は、バックグラウンドワーカーが作業を完了した(または、場合によってはキャンセルが完了した)かどうかを繰り返し確認するタイマーを備えたモーダルダイアログを表示することです。バックグラウンドワーカーが完了すると、モーダルダイアログは制御をアプリケーションに戻します。これが発生するまで、ユーザーはUIを操作できません。

別の方法(最大で1つのモードレスウィンドウを開いていると想定)は、ActiveForm.Enabled = falseを設定し、バックグラウンドワーカーがキャンセルを完了するまでApplication、DoEventsでループし、その後ActiveForm.Enabled = trueを再び設定できます。


5
それは問題になるかもしれませんが、「BackgroundWorkerがキャンセルするのを待つ方法」という質問の一部として基本的に受け入れられています。待つことは待つことを意味し、他に何もしません。これには、メッセージの処理も含まれます。バックグラウンドワーカーを待ちたくない場合は、.CancelAsyncを呼び出すだけです。しかし、それはここでの設計要件ではありません。
Ian Boyd

1
+1は、CancelAsyncメソッドの値を示します。これは、バックグラウンドワーカーを待機するのとは異なります。
Ian Boyd

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

ワーカーがキャンセルされるのを待つことができない場合、3つのメソッドすべてをRunWorkerCompletedEventに移動する必要があります。

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;

    if (delayedBlowUpRocket)
        BlowUpRocket();
    else if (delayedOpenAccessGates)
        OpenAccessGates();
    else if (delayedDrainRocket)
        DrainRocket();
}

private void BlowUpRocket()
{
    if (worker != null)
    {
        delayedBlowUpRocket = true;
        worker.CancelAsync();
        return;
    }

    StartClaxon();
    SelfDestruct();
}

private void OpenAccessGates()
{
    if (worker != null)
    {
        delayedOpenAccessGates = true;
        worker.CancelAsync();
        return;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

private void DrainRocket()
{
    if (worker != null)
    {
        delayedDrainRocket = true;
        worker.CancelAsync();
        return;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

今はそのようなコードを書くことができましたが、私はただつもりではありません。私は気にしません、私はそうではありません。


18
WaitForWorkerToFinishメソッドはどこにありますか?完全なソースコードはありますか?
Kiquenet

4

あなたはにチェックインすることができますRunWorkerCompletedEventArgsRunWorkerCompletedEventHandler状態だったかを確認します。成功、キャンセル、またはエラー。

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

更新:ワーカーがこれを使用して.CancelAsync()を呼び出したかどうかを確認するには:

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

2
要件は、CancelDoingStuff()がワーカーが完了するまで戻ることができないことです。それがどのように完了したかを確認することは、実際には重要ではありません。
Ian Boyd

次に、イベントを作成する必要があります。具体的には、BackgroundWorkerとは何の関係もありません。イベントを実装し、それをリッスンして、完了したら起動するだけです。そして、RunWorkerCompletedEventHandlerが完了したときです。別のイベントを起動します。
Seb Nilsson

4

あなたはしていない、完全にバックグラウンドワーカーを待ちます。これは、別のスレッドを起動する目的をかなり損なうものです。代わりに、メソッドを終了させ、完了に依存するコードを別の場所に移動する必要があります。完了したらワーカーに通知して、残りのコードを呼び出します。

何かが完了するのを待つ場合は、WaitHandleを提供する別のスレッド構成を使用してください。


1
提案してもらえますか?バックグラウンドワーカーは、BackgroundWorkerオブジェクトを作成したスレッドに通知を送信できる唯一のスレッドコンストラクトのようです。
Ian Boyd

backgroundworkerはUI構造です。それはあなた自身のコードがまだ呼び出す方法を知る必要があるイベントを使うだけです。そのための独自のデリゲートを作成することを妨げるものは何もありません。
Joel Coehoorn 2008

3

なぜBackgroundWorker.RunWorkerCompletedイベントに結び付けられないのでしょうか。これは、「バックグラウンド操作が完了したか、キャンセルされたか、例外が発生したときに発生する」コールバックです。


1
それは、DoesStuffオブジェクトを使用しているユーザーがキャンセルを要求したためです。オブジェクトが使用する共有リソースは、切断、削除、破棄、クローズされようとしています。先に進むには、それが完了したことを知る必要があります。
Ian Boyd

1

BackgroundWorkerの完了を待つ理由を理解できません。クラスのモチベーションとは正反対のようです。

ただし、worker.IsBusyを呼び出してすべてのメソッドを開始し、実行中の場合は終了させることができます。


言葉の選択が悪い; 完了するのを待っていません。
Ian Boyd

1

ループ中に非同期プロセスを実行している間、バックグラウンドワーカーが待機する必要があるため、ここに来たと言いたいだけですが、他のすべてのものよりも簡単に修正できました^^

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

これは私が解決策を探している間に私が終わった場所だからです。また、これはスタックオーバーフローに関する私の最初の投稿です。:)


0

たぶん私はあなたの質問を正しく理解していません。

backgroundworkerは、「workermethod」(backgroundworker.doWork-eventを処理するメソッド/関数/サブルーチン)が終了するとWorkerCompletedイベントを呼び出す ため、BWがまだ実行されているかどうかを確認する必要はありません。ワーカーを停止する場合は、「ワーカーメソッド」内のキャンセル保留プロパティを確認してください。


ワーカーがCancellationPendingプロパティを監視して、できるだけ早く終了する必要があることを理解しています。しかし、バックグラウンドワーカーにキャンセルを要求した外部の人は、それが完了するのをどのように待つのでしょうか。
Ian Boyd

WorkCompletedイベントは引き続き発生します。
Joel Coehoorn 2008

「しかし、バックグラウンドワーカーにキャンセルを要求した外部の人は、それが完了するのをどのように待つのですか?」
Ian Boyd

WorkCompletedイベントが発生するのを待って、それが完了するのを待ちます。ユーザーがGUI全体でクリックフィットしないようにする場合は、@ Joeが上記の回答で提案したソリューションの1つを使用できます(フォームを無効にするか、モーダルを表示します)。言い換えると、システムのアイドルループに待機させます。それは(イベントを発動することにより)それが終了したことを教えてくれます。
Geoff 2013年

0

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。それでも、それでも一種の不器用です。cancelイベントを非同期メソッドのように扱い、RunWorkerCompletedハンドラーで現在実行していることをすべて実行することをお勧めします。


RunWorkerCompletedにコードを移動することは、コードがどこに属しているかではなく、また見栄えもよくありません。
Ian Boyd

0

私はここでパーティーに少し遅れますが(約4年)、UIをロックせずにビジーループを処理できる非同期スレッドを設定してから、そのスレッドからのコールバックで、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、我々は成功したのキャンセルを実行する必要がありどんな方法で実行すること-それは擬似イベントのように動作します。これはUIスレッドとは別なので、MyWorkerキャンセルが完了するのを待つ間、UIをロックしません。あなたの意図が本当にロックしてキャンセルを待つことであるならば、これはあなたにとって役に立たないですが、あなたが単に待って別のプロセスを開始できるようにしたければ、これはうまくいきます。


0

私はこれが本当に遅い(5年)ことを知っていますが、探しているのはThreadとSynchronizationContextを使用することです。フレームワークに自動的に実行させるのではなく、UI呼び出しを「手動で」UIスレッドにマーシャリングする必要があります。

これにより、必要に応じて待機できるスレッドを使用できます。


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

あなたの質問は何ですか?SOでは、質問をするときに具体的にする必要があります
Alma Do

@これは質問ではなく答えです。
コードルーバー2013

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();は呼び出されない可能性があります。

_resetEvent.Set();DoWorkハンドラーで例外が発生した場合でも、try-finallyはそれが呼び出されることを保証します。そうしないと、呼び出し時にアプリケーションが永久にフリーズする可能性があります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 documentation)


0

フォームを閉じると、開いているログファイルが閉じます。バックグラウンドワーカーがそのログファイルを書き込むのでMainWin_FormClosing()、バックグラウンドワーカーが終了するまで終了させることができません。バックグラウンドワーカーが終了するのを待たないと、例外が発生します。

なぜこれがそんなに難しいのですか?

単純な方法でもThread.Sleep(1500)機能しますが、シャットダウンが遅くなる(長すぎる場合)、または例外が発生する(短すぎる場合)。

バックグラウンドワーカーが終了した直後にシャットダウンするには、変数を使用します。これは私のために働いています:

private volatile bool bwRunning = false;

...

private void MainWin_FormClosing(Object sender, FormClosingEventArgs e)
{
    ... // Clean house as-needed.

    bwInstance.CancelAsync();  // Flag background worker to stop.
    while (bwRunning)
        Thread.Sleep(100);  // Wait for background worker to stop.
}  // (The form really gets closed now.)

...

private void bwBody(object sender, DoWorkEventArgs e)
{
    bwRunning = true;

    BackgroundWorker bw = sender as BackgroundWorker;

    ... // Set up (open logfile, etc.)

    for (; ; )  // infinite loop
    {
        ...
        if (bw.CancellationPending) break;
        ...
    } 

    ... // Tear down (close logfile, etc.)

    bwRunning = false;
}  // (bwInstance dies now.)

0

RunWorkerCompletedイベントから便乗できます。_workerのイベントハンドラーをすでに追加している場合でも、それらを追加した順に実行する別のイベントハンドラーを追加できます。

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

    ...

    public void CancelDoingStuff()
    {
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((sender, e) => 
        {
            // do whatever you want to do when the cancel completes in here!
        });
        _worker.CancelAsync();
    }
}

これは、キャンセルが発生する複数の理由がある場合に役立ち、単一のRunWorkerCompletedハンドラーのロジックが必要以上に複雑になります。たとえば、ユーザーがフォームを閉じようとしたときにキャンセルすると、次のようになります。

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

0

私はasyncメソッドを使用awaitして、ワーカーが仕事を終えるのを待ちます:

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

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

そしてDoWorkメソッドで:

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

また、カプセル化してwhileループをDoWork持つtry ... catch集合に_isBusyあるfalse例外に。または、whileループをチェックイン_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. await Task.Delayは不正確です。私のPCでは、Task.Delay(1)は実際には最大20ms待機します。

-2

ああ、これらのいくつかは途方もなく複雑になっています。DoWorkハンドラー内のBackgroundWorker.CancellationPendingプロパティを確認するだけです。いつでも確認できます。保留状態になったら、e.Cancel = Trueに設定し、メソッドからベイルします。

//ここのメソッドprivate void Worker_DoWork(object sender、DoWorkEventArgs e){BackgroundWorker bw =(sender as BackgroundWorker);

// do stuff

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

// do other stuff

}


1
また、このソリューションでは、CancelAsyncを呼び出したユーザーは、バックグラウンドワーカーがキャンセルするまで待機するように強制されますか?
Ian Boyd
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.