127

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

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

ただし、イベントが処理されるまでクリアされず、アプリケーションがアイドル状態になるまでそのイベントを処理できないため、これはデッドロックになります。ワーカーが完了するまで、アプリケーションはアイドル状態になりません。(さらに、それは忙しいループです - 嫌です。)IsBusyRunWorkerCompleted

他の人は、それを次のように提案しました:

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

その問題は、Application.DoEvents()現在キューにあるメッセージが処理され、再入の問題が発生することです (.NET は再入可能ではありません)。

コードがイベントを待機するイベント同期オブジェクトを含むいくつかのソリューションを使用したいと考えています-ワーカーのRunWorkerCompletedイベントハンドラーが設定します。何かのようなもの:

Event _workerDoneEvent = new WaitHandle();

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

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

しかし、デッドロックに戻りました。アプリケーションがアイドル状態になるまでイベント ハンドラーを実行できず、アプリケーションはイベントを待っているためアイドル状態になりません。

では、BackgroundWorker が終了するのをどのように待つことができるでしょうか?


更新 人々はこの質問に混乱しているようです。彼らは、私が BackgroundWorker を次のように使用すると考えているようです。

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

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

4

18 に答える 18

131

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

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
}
于 2008-09-23T20:52:17.807 に答える
16

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

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

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

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

于 2008-09-24T11:22:50.367 に答える
9

ほとんどの人は質問に混乱していて、労働者がどのように使われているかを理解していません。

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

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

于 2008-09-24T14:09:27.720 に答える
4

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

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

于 2008-09-23T20:43:42.023 に答える
4

RunWorkerCompletedEventHandlerRunWorkerCompletedEventArgsをチェックインして、ステータスを確認できます。成功、キャンセル、またはエラー。

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");
}
于 2008-09-23T20:36:31.353 に答える
3

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

于 2008-09-23T20:39:39.020 に答える
1

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

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

解決策を探しているときにここにたどり着いたので、共有したいと思いました。また、これはスタック オーバーフローに関する私の最初の投稿です。:)

于 2015-06-18T16:30:01.293 に答える
1

BackgroundWorker が完了するまで待ちたい理由がわかりません。それは本当に授業の動機とは正反対のようです。

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

于 2008-09-23T20:41:31.577 に答える
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が成功した後に必要なメソッドを実行するために使用できます。これは、疑似イベントのように機能します。MyWorkerこれはUIスレッドとは別のものであるため、キャンセルが完了するのを待つ間、UIはロックされません。あなたの意図が本当にロックしてキャンセルを待つことであるなら、これはあなたには役に立たないが、あなたがただ待って別のプロセスを始めることができるようにしたいなら、これはうまくいく。

于 2012-07-06T23:08:24.720 に答える
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してフラグを設定することをお勧めします。ただし、それはまだちょっと厄介です。RunWorkerCompletedキャンセル イベントを非同期メソッドのように扱い、ハンドラーで現在実行していることは何でも実行することをお勧めします。

于 2008-09-23T21:02:45.207 に答える
0

うーん、私はあなたの質問を正しく理解していないかもしれません。

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

于 2008-09-23T20:42:53.007 に答える
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 クラス (Microsoft ドキュメント)

于 2014-06-27T17:54:46.327 に答える
-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

}

于 2008-09-24T19:57:11.160 に答える