4

メソッドに関するMSDNの記事BackgroundWorker.CancelAsyncは、次のような注意があります。

DoWorkイベント ハンドラーのコードは、キャンセル リクエストが行われているときに作業を終了する可能性があり、ポーリング ループがtrueCancellationPendingに設定されない可能性があることに注意してください。この場合、キャンセル リクエストが行われたとしても、イベント ハンドラのフラグは trueに設定されません。この状況は 競合状態と呼ばれ、マルチスレッド プログラミングでは一般的な問題です。CancelledSystem.ComponentModel.RunWorkerCompletedEventArgsRunWorkerCompleted

この競合状態を回避するための正しい解決策は何ですか?

これは、この状態の外観を引き起こす私のサンプルコードです。

public partial class MainWindow : Window
{
    BackgroundWorker bW;
    int bWsCount = 0;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_MouseMove(object sender, MouseEventArgs e)
    {
        if (bW != null)
            bW.CancelAsync();
        bW = new BackgroundWorker();
        bW.WorkerSupportsCancellation = true;
        bW.DoWork += new DoWorkEventHandler(bW_DoWork);
        bW.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bW_RunWorkerCompleted);
        bW.RunWorkerAsync(0);
        bWsCount++;
        labelBackgroundWorkersCount.Content = bWsCount;
    }

    void bW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        if (e.Cancelled || worker.CancellationPending)
        {
            bWsCount--;
            labelBackgroundWorkersCount.Content = bWsCount;
        }
        else
            worker.RunWorkerAsync(1000);
    }

    void bW_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        int sleepDuration = Convert.ToInt32(e.Argument);
        if (worker.CancellationPending) { e.Cancel = true; return; }
        Thread.Sleep(sleepDuration);
        if (worker.CancellationPending) { e.Cancel = true; return; }
    }
}
4

1 に答える 1

4

これを回避する方法はありません。すべてのスレッドの問題に解決策があるわけではありません。これらのことは、極限まで推し進めると推論できます。ワーカー スレッドが完了する 1ナノ秒前に、UI スレッドのコードが CancelAsync() を呼び出すとします。スレッドが最後の機械語命令を実行しているのと同じように。明らかに、これがスレッドによって検出される可能性はありません。また、スレッドが最後の命令にあることを UI スレッドが確認する方法もありません。

これは実際の問題ではありません。RunWorkerCompleted イベント ハンドラを作成するときは、このことを念頭に置いてください。e.Cancelled プロパティが false の場合、ワーカーはとにかく完了しました。必要に応じて、CancelAsync() を呼び出すときに true に設定した別の bool で単純に && することができます。だいたい:

    private bool cancelRequested;

    private void CancelButton_Click(object sender, EventArgs e) {
        cancelRequested = true;
        backgroundWorker1.CancelAsync();
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        if (e.Error != null) throw e.Error;
        if (!e.Cancelled && !cancelRequested) {
            // Completed entirely normally
            //...
        }
    }

これは単なるコードの競合ではないことに注意してください。はるかに実際的な問題は、これがユーザーの脳内の競争でもあるということです。ユーザーが [キャンセル] ボタンをクリックする 1 ミリ秒前にすべてが正常に完了するという失敗モードを使用します。

したがって、ユーザーが知る限り、実際にはキャンセルされていない操作がキャンセルされました。これは不快な結果をもたらす可能性があり、実際に何が起こったのかをユーザーに伝えることで対処する必要があります。この場合、コード スニペットを使用してもまったく意味がありません。操作をキャンセルして完了させるだけであれば、同様に機能します。

于 2012-04-26T08:34:56.107 に答える