並列または順番に実行したい 2 つの .net Task オブジェクトがあります。どちらの場合でも、スレッドをブロックして待機させたくありません。結局のところ、Reactive Extensionsによって、並列のストーリーが単純に美しくなります。しかし、タスクを順番に並べようとすると、コードは機能しますが、ぎこちなく感じます。
逐次バージョンをより簡潔にする方法や、並列バージョンと同じくらい簡単にコーディングする方法を誰かが示すことができるかどうか知りたいです。この質問への回答にリアクティブ拡張機能を使用する必要はありません。
参考までに、並列処理と順次処理の両方に対する私の 2 つのソリューションを次に示します。
並列処理版
これは純粋な喜びです。
public Task<string> DoWorkInParallel()
{
var result = new TaskCompletionSource<string>();
Task<int> AlphaTask = Task.Factory.StartNew(() => 4);
Task<bool> BravoTask = Task.Factory.StartNew(() => true);
//Prepare for Rx, and set filters to allow 'Zip' to terminate early
//in some cases.
IObservable<int> AsyncAlpha = AlphaTask.ToObservable().TakeWhile(x => x != 5);
IObservable<bool> AsyncBravo = BravoTask.ToObservable().TakeWhile(y => y);
Observable
.Zip(
AsyncAlpha,
AsyncBravo,
(x, y) => y.ToString() + x.ToString())
.Timeout(TimeSpan.FromMilliseconds(200)).Subscribe(
(x) => { result.TrySetResult(x); },
(x) => { result.TrySetException(x.GetBaseException()); },
() => { result.TrySetResult("Nothing"); });
return result.Task;
}
順次/パイプライン処理バージョン
これは機能しますが、不器用です:
public Task<string> DoWorkInSequence()
{
var result = new TaskCompletionSource<string>();
Task<int> AlphaTask = Task.Factory.StartNew(() => 4);
AlphaTask.ContinueWith(x =>
{
if (x.IsFaulted)
{
result.TrySetException(x.Exception.GetBaseException());
}
else
{
if (x.Result != 5)
{
Task<bool> BravoTask = Task.Factory.StartNew(() => true);
BravoTask.ContinueWith(y =>
{
if (y.IsFaulted)
{
result.TrySetException(y.Exception.GetBaseException());
}
else
{
if (y.Result)
{
result.TrySetResult(x.Result.ToString() + y.Result.ToString());
}
else
{
result.TrySetResult("Nothing");
}
}
});
}
else
{
result.TrySetResult("Nothing");
}
}
}
);
return result.Task;
}
上記のシーケンシャル コードでは、ごちゃごちゃになっていて、並列バージョンに合わせて タイムアウト機能を追加していません。
要件(8/6更新)
回答者は、次の点に注意してください。
シーケンシャル シナリオでは、最初のタスクの出力が 2 番目のタスクの入力に供給される配置を許可する必要があります。上記の私のサンプルの「ぎこちない」コードは、それを実現するために簡単に配置できたはずです。
私は.net 4.5の回答に興味がありますが、.net 4.0の回答は私にとって同等以上に重要です。
タスク「アルファ」と「ブラボー」には、完了までに合わせて 200 ミリ秒の時間制限があります。それぞれ200ミリ秒ありません。これはシーケンシャルの場合にも当てはまります。
いずれかのタスクが無効な結果を返す場合、SourceCompletionTask は、両方のタスクが完了する前に早期に完了する必要があります。サンプル コードの明示的なテストで示されているように、無効な結果は [AlphaTask:5] または [BravoTask:false] のいずれかです。
更新 8/8: 明確化- シーケンシャルの場合、AlphaTask が成功しない場合、またはタイムアウトが既に発生している場合、BravoTask はまったく実行されません。AlphaTask と BravoTask の両方がブロックできないと仮定します。それは問題ではありませんが、私の現実のシナリオでは、実際には非同期の WCF サービス呼び出しです。
シーケンシャル バージョンをクリーンアップするために利用できた Rx の側面があるのかもしれません。しかし、タスクプログラミングだけでも、私が想像するより良い話があるはずです。見てみましょう。
正誤表 両方のコード サンプルで、 TaskCompletionSourceを返すべきではなかったポスターの回答が非常に正しかったため、戻り値の型を Task に変更しました。