2 つの操作を実行する必要がある文字列のコレクションがあります。
これらの最初のものは、任意の順序で独立して安全に処理できますが (yay)、出力は元の順序で順次処理する必要があります (boo)。
次の Plinq は、ほとんどの方法で私をそこに連れて行きます。
myStrings.AsParallel().AsOrdered()
.Select( str => Operation1(str) )
.AsSequential()
.Select( str => Operation2(str) );
//immagine Operation2() maintains some sort of state and must take the outputs from Operation1 in the original order
これで大体のところはわかったのですが、問題は、AsOrdered() が原因で、最初にすべての文字列に対して Operation1 が実行され、次に結果要素が元の順序に並べ替えられ、最後に Operation2 の実行が開始されることです。
理想的には、最初の文字列 (つまり、最初に返された文字列ではなく、myStrings[0]) が Operation1 呼び出しによって返されたらすぐに、Operation2 がその作業を開始するようにしたいと考えています。
したがって、これは問題を一般的に解決するための私の試みです:
public static class ParallelHelper
{
public static IEnumerable<U> SelectAsOrdered<T, U>(this ParallelQuery<T> query, Func<T, U> func)
{
var completedTasks = new Dictionary<int, U>();
var queryWithIndexes = query.Select((x, y) => new { Input = x, Index = y })
.AsParallel()
.Select(t => new { Value = func(t.Input), Index = t.Index })
.WithMergeOptions(ParallelMergeOptions.NotBuffered);
int i = 0;
foreach (var task in queryWithIndexes)
{
if (i==task.Index)
{
Console.WriteLine("immediately yielding task: {0}", i);
i++;
yield return task.Value;
U previouslyCompletedTask;
while (completedTasks.TryGetValue(i, out previouslyCompletedTask))
{
completedTasks.Remove(i);
Console.WriteLine("delayed yielding task: {0}", i);
yield return previouslyCompletedTask;
i++;
}
}
else
{
completedTasks.Add(task.Index, task.Value);
}
}
yield break;
}
}
次に、元のコード ブロックを次のように書き直すことができます。
myStrings.AsParallel()
.SelectAsOrdered( str => Operation1(str) )
.Select(str => Operation2(str));
Operation2 は、myStrings[0] が Operation1 から出るとすぐに開始されます。
私が知りたいのは:
- これは、並列化におけるかなり一般的な問題/パターンです。それとももっと簡単な方法がありますか?
- 上記の拡張メソッドは機能しているように見えますが、どのように改善できるのでしょうか? コード内に悪い考えのように見えるものはありますか?
ありがとう!
アンディ
興味がある場合に備えて:
.WithMergeOptions(ParallelMergeOptions.NotBuffered) への呼び出しがなければ、すべての Operation1 呼び出しが開始されるまで、Operation2 はその作業を開始しません (これは、すべてが完了するまで待機した元のコードよりも優れています)。
実際の問題:
操作 1 は、大量のテキスト内で法的な引用と参照を検索しています (例: "children act 1989")。
これらの参照は通常独立していますが、時折、トランスクリプトに「前述の行為のセクション 6」のようなものが含まれることがあります。Operation2 は、Operation1 からのキャプチャに依存して、これらの部分参照を取得します。