ユーザーがキャンセルできる操作を実行するためにTPL
ブロックを使用しています。次の2つのオプションが考えられます。最初に、ブロック全体をキャンセルしますが、ブロック内の操作はキャンセルしません。
_downloadCts = new CancellationTokenSource();
var processBlockV1 = new TransformBlock<int, List<int>>(construct =>
{
List<int> properties = GetPropertiesMethod(construct );
var entities = properties
.AsParallel()
.Select(DoSometheningWithData)
.ToList();
return entities;
}, new ExecutionDataflowBlockOptions() { CancellationToken = _downloadCts.Token });
2番目は、内部の操作をキャンセルしますが、ブロック自体はキャンセルしません。
var processBlockV2 = new TransformBlock<int, List<int>>(construct =>
{
List<int> properties = GetPropertiesMethod(construct);
var entities = properties
.AsParallel().WithCancellation(_downloadCts.Token)
.Select(DoSometheningWithData)
.ToList();
return entities;
});
私が理解しているように、最初のオプションはブロック全体をキャンセルし、パイプライン全体をシャットダウンします。私の質問は、操作内でキャンセルし、すべてのリソースがある場合はそれを破棄するか(オープンStreamReadersなど)か、2番目のオプションを選択する方が良いですか?いくつかの手段(鉄道プログラミング)を上げてOperationCanceledException
をパイプにフロートさせ、必要な場所で処理しますか?
これら2つのオプションは同等ではありません。
最初のオプション(CancellationToken = _downloadCts.Token
)は、processBlockV1
ブロックが現在そのバッファーにあるメッセージを破棄するようにし(その InputCount
プロパティは0
になります)、新しいメッセージの受け入れを停止します(その呼び出し Post
メソッドは常にfalse
)を返します。ただし、現在進行中のメッセージの処理は停止しません。これらは完全に処理されますが、リンクされたブロックには伝達されません。これらのメッセージが処理された後、ブロックはキャンセルされた状態で完了します(その Completion
.Status
プロパティはCanceled
になります)。
2番目のオプション(内部操作のキャンセル)は、ブロック全体に影響を与えません。 Dataflowブロックは、処理関数からスローされた OperationCanceledException
を許容し、障害のあるアイテムを無視して次のアイテムに進みます。したがって、トークンのキャンセル後も、投稿されたすべてのメッセージが処理され、ブロックは引き続きそれ以上受け入れます。すべてのアイテムがOperationCanceledException
をスローして無視されるため、リンクされたブロックには何も伝播しません。特定の例では、GetPropertiesMethod
メソッドがすべてのconstruct
メッセージに対して呼び出されるため、ブロックの完了に遅延が発生します。ブロックの最終状態はRanToCompletion
になります。
DataflowブロックがCompletion
の概念を真剣に受け止めていることを理解することが重要です。完了を報告する前に、実行が停止することがわかっているすべてを待機します。それらを時期尚早に完了させ、まだ実行中のタスクを残したい場合は、 キャンセル可能なラッパーでタスクをラップする のようなトリックを実行する必要があります。