すべて、C# で Task:s をキャンセルする複雑なケースの設計/ベスト プラクティスに関する質問です。共有タスクのキャンセルをどのように実装しますか?
最小限の例として、次のことを想定してみましょう。長時間実行され、協調的にキャンセル可能な操作「Work」があります。キャンセル トークンを引数として受け取り、キャンセルされた場合はスローします。一部のアプリケーションの状態で動作し、値を返します。その結果は、2 つの UI コンポーネントによって個別に必要とされます。
アプリケーションの状態が変更されていない間は、Work 関数の値をキャッシュする必要があります。1 つの計算が進行中の場合、新しい要求は 2 番目の計算を開始するのではなく、結果の待機を開始する必要があります。
どちらの UI コンポーネントも、他の UI コンポーネント タスクに影響を与えることなく、そのタスクをキャンセルできる必要があります。
ここまで一緒ですか?
上記は、実際の Work タスクを TaskCompletionSources にラップする Task キャッシュを導入することで実現できます。その Task:s は UI コンポーネントに返されます。UI コンポーネントがそのタスクをキャンセルすると、TaskCompletionSource タスクのみが破棄され、基になるタスクは破棄されません。これはすべて良いです。UI コンポーネントは CancellationSource を作成し、キャンセル要求は通常のトップダウン設計であり、下部に連携する TaskCompletionSource タスクがあります。
さて、本当の問題に。アプリケーションの状態が変化した場合の対処方法 'Work' 関数を状態のコピーで動作させることは実現不可能であると仮定しましょう。
1 つの解決策は、タスク キャッシュ (またはそのあたり) の状態の変化をリッスンすることです。基になるタスク (Work 関数を実行するタスク) によって使用される CancellationToken がキャッシュにある場合、それをキャンセルできます。これにより、アタッチされたすべての TaskCompletionSources Task:s のキャンセルがトリガーされる可能性があり、したがって、両方の UI コンポーネントが Canceled タスクを取得します。これはある種のボトムアップキャンセルです。
これを行うための好ましい方法はありますか?どこかにそれを説明するデザインパターンはありますか?
ボトムアップキャンセルは実装できますが、ちょっと変な感じです。UI タスクは CancellationToken で作成されますが、別の (内部の) CancellationToken によりキャンセルされます。また、トークンが同じではないため、UI で OperationCancelledException を無視することはできません。これにより、(最終的に) 外側の Task:s ファイナライザーで例外がスローされます。