web-dev-qa-db-ja.com

TPLデータフローブロックをキャンセルする正しい方法

ユーザーがキャンセルできる操作を実行するために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をパイプにフロートさせ、必要な場所で処理しますか?

5
niks

これら2つのオプションは同等ではありません。

  1. 最初のオプション(CancellationToken = _downloadCts.Token)は、processBlockV1ブロックが現在そのバッファーにあるメッセージを破棄するようにし(その InputCount プロパティは0になります)、新しいメッセージの受け入れを停止します(その呼び出し Post メソッドは常にfalse)を返します。ただし、現在進行中のメッセージの処理は停止しません。これらは完全に処理されますが、リンクされたブロックには伝達されません。これらのメッセージが処理された後、ブロックはキャンセルされた状態で完了します(その Completion.Status プロパティはCanceledになります)。

  2. 2番目のオプション(内部操作のキャンセル)は、ブロック全体に影響を与えません。 Dataflowブロックは、処理関数からスローされた OperationCanceledException を許容し、障害のあるアイテムを無視して次のアイテムに進みます。したがって、トークンのキャンセル後も、投稿されたすべてのメッセージが処理され、ブロックは引き続きそれ以上受け入れます。すべてのアイテムがOperationCanceledExceptionをスローして無視されるため、リンクされたブロックには何も伝播しません。特定の例では、GetPropertiesMethodメソッドがすべてのconstructメッセージに対して呼び出されるため、ブロックの完了に遅延が発生します。ブロックの最終状態はRanToCompletionになります。

DataflowブロックがCompletionの概念を真剣に受け止めていることを理解することが重要です。完了を報告する前に、実行が停止することがわかっているすべてを待機します。それらを時期尚早に完了させ、まだ実行中のタスクを残したい場合は、 キャンセル可能なラッパーでタスクをラップする のようなトリックを実行する必要があります。

3
Theodor Zoulias