あなたのアプローチを基礎として、少し適応させました:
var source = new CancellationTokenSource();
var token = source.Token;
var taskA = Task.Factory.StartNew(
() => OperationA()
);
var taskAFinished = taskA.ContinueWith(antecedent =>
{
source.Cancel();
return antecedent.Result;
});
var taskB = Task.Factory.StartNew(
() => OperationB(token), token
);
var taskBFinished = taskB.ContinueWith(antecedent =>
{
switch (antecedent.Status)
{
case TaskStatus.RanToCompletion:
case TaskStatus.Canceled:
try
{
return ant.Result;
}
catch (AggregateException ae)
{
// Operation was canceled before start if OperationA is short
return null;
}
case TaskStatus.Faulted:
return null;
}
return null;
});
それぞれの操作の結果を返す 2 つの継続を作成したので、両方が完了するのを待つことができました (2 番目の操作でのみ実行しようとしましたが、うまくいきませんでした)。
var tasks = new Task[] { taskAFinished, taskBFinished };
Task.WaitAll(tasks);
1 つ目は先行タスクの結果をさらに渡すだけで、2 つ目は OperationB のこの時点で利用可能な集計結果を取得します (RanToCompletion と Canceled の両方のステータスは、プロセスの正しい終了と見なされます)。OperationB は次のようになります。
public static List<Result> OperationB(CancellationToken token)
{
var resultsList = new List<Result>();
while (true)
{
foreach (var op in operations)
{
resultsList.Add(op.GetResult();
}
if (token.IsCancellationRequested)
{
return resultsList;
}
}
}
ロジックを少し変更 - OperationB 内のすべてのループは単一のタスクと見なされるようになりましたが、これは、それらをアトミックに保ち、各実行から結果を収集する何らかの調整プリミティブを記述するよりも簡単です。どのループがどの結果を生成したかをあまり気にしない場合、これはまともな解決策のようです。必要に応じて、後でより柔軟な実装に改善される可能性があります (私が実際に探していたのは、複数の操作を再帰的にチェーンすることです。OperationB 自体は、内部で同じ動作をする OperationC の繰り返しを小さくすることができます。OperationC - C がアクティブなときに実行されている複数の OperationD など)。
edit
OperationA が迅速で、OperationB が開始される前にキャンセルが発行された場合に備えて、taskBFinished に例外処理を追加しました。