4

いくつかの結果を生成するボタンがあるWinformsアプリケーションについて考えてみます。ユーザーがボタンを2回押すと、最初のリクエストをキャンセルして結果を生成し、新しいリクエストを開始する必要があります。

以下のパターンを使用していますが、競合状態を防ぐためにコードの一部が必要かどうかはわかりません(コメントアウトされた行を参照)。

    private CancellationTokenSource m_cts;

    private void generateResultsButton_Click(object sender, EventArgs e)
    {
        // Cancel the current generation of results if necessary
        if (m_cts != null)
            m_cts.Cancel();
        m_cts = new CancellationTokenSource();
        CancellationToken ct = m_cts.Token;

        // **Edit** Clearing out the label
        m_label.Text = String.Empty;
        // **Edit**

        Task<int> task = Task.Run(() =>
        {
            // Code here to generate results.
            return 0;
        }, ct);

        task.ContinueWith(t =>
        {
            // Is this code necessary to prevent a race condition?
            // if (ct.IsCancellationRequested)
            //     return;

            int result = t.Result;
            m_label.Text = result.ToString();
        }, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
    }

知らせ:

  • CancellationTokenSourceメインスレッドでのみキャンセルします。
  • CancellationToken継続では、元のタスクと同じものを使用します。

次の一連のイベントが可能かどうか疑問に思っています。

  1. ユーザーが「結果の生成」ボタンをクリックします。初期タスクt1が開始されます。
  2. ユーザーは「結果の生成」ボタンをもう一度クリックします。Windowsメッセージはキューに送信されますが、ハンドラーはまだ実行されていません。
  3. タスクt1が終了します。
  4. TPL startは、継続を開始する準備をします(まだ キャンセルCancellationTokenされていないため)。タスクスケジューラは、作業をWindowsメッセージキューに送信します(メインスレッドで実行するため)。
  5. 2回目のクリックのgenerateResultsButton_Clickが実行を開始し、CancellationTokenSourceがキャンセルされます。
  6. 継続作業が開始され、トークンがキャンセルされなかったかのように動作します(つまり、UIに結果が表示されます)。

だから、私は質問が要約すると思います:

作業がメインスレッドに投稿されると(を使用してTaskScheduler.FromCurrentSynchronizationContext())、TPLCancellationTokenはタスクのアクションを実行する前にメインスレッドでをチェックしますか、またはそれが起こっているスレッドでキャンセルトークンをチェックしてから、作業をに投稿しますSynchronizationContext

4

2 に答える 2

5

私が質問を正しく読んだと仮定すると、あなたは次の一連の出来事について心配しています:

  1. ボタンがクリックされ、タスクT0がスレッドプールでC0スケジュールされ、継続がの継続としてスケジュールさT0れ、同期コンテキストのタスクスケジューラで実行されます
  2. ボタンをもう一度クリックします。メッセージポンプが他のことをするのに忙しいとしましょう。メッセージキューは1つのアイテム、クリックハンドラで構成されます。
  3. T0完了C0すると、メッセージキューに投稿されます。キューには、クリックハンドラーとの実行という2つの項目が含まれるようになりましたC0
  4. クリックハンドラメッセージが送信され、ハンドラはT0とのキャンセルを駆動するトークンに信号を送りますC0。次に、ステップと同じ方法でT1、スレッドプールと継続としてスケジュールします。C11
  5. 'execute C0'メッセージはまだキューにあるため、ここで処理されます。キャンセルしようとした継続を実行しますか?

答えはいいえだ。TryExecuteTaskは、キャンセルの通知を受けたタスクを実行しません。それはそのドキュメントによって暗示されていますが、TaskStatusページで明示的に説明されています。

キャンセル済み-トークンがシグナル状態のときに、タスクが独自のCancellationTokenを指定してOperationCanceledExceptionをスローするか、タスクの実行が開始される前にタスクのCancellationTokenがすでにシグナルされていることにより、タスクがキャンセルを確認しました。

したがって、1日の終わりには、州になりT0RanToCompletion州にC0なりますCanceled

もちろん、これはすべて、現在のSynchronizationContextタスクでタスクの同時実行が許可されていないことを前提としています(ご存知のとおり、Windowsフォームでは許可されていません。これは同期コンテキストの要件ではないことに注意してください)。

また、キャンセルが要求されたとき、またはタスクが実行されたときにキャンセルトークンがチェックされるかどうかについての最後の質問に対する正確な答えは、実際には両方であることに注意してください。最終チェックインに加えて、TryExecuteTaskキャンセルが要求されるとすぐに、フレームワークはTryDequeue、タスクスケジューラがサポートできるオプションの操作を呼び出します。同期コンテキストスケジューラはそれをサポートしていません。しかし、どういうわけかそうなった場合、違いは、「execute C0」メッセージがスレッドのメッセージキューから完全に削除され、タスクを実行しようとさえしないことです。

于 2013-03-28T02:13:20.200 に答える
-1

私の見方では、どのスレッドがCencellationTokenをチェックするかに関係なく、継続がスケジュールされ、継続の実行中にユーザーがリクエストをキャンセルできる可能性を考慮する必要があります。したがって、コメントアウトされたチェックをチェックする必要があり、結果を読んだ後でもう一度チェックする必要があると思います。

        task.ContinueWith(t =>
    {
        // Is this code necessary to prevent a race condition?
        if (ct.IsCancellationRequested)
            return;

        int result = t.Result;

        if (ct.IsCancellationRequested)
            return;

        m_label.Text = result.ToString();
    }, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());

また、キャンセル条件を個別に処理するために継続を追加します。

        task.ContinueWith(t =>
    {
        // Do whatever is appropriate here.

    }, ct, TaskContinuationOptions.OnlyOnCanceled, TaskScheduler.FromCurrentSynchronizationContext());

このようにして、すべての可能性をカバーできます。

于 2013-03-27T15:55:35.230 に答える