PLINQ を使用して、シリアル ポートをテストして GPS デバイスかどうかを判断する機能を実行しています。
一部のシリアル ポートは、すぐに有効な GPS であることがわかります。この場合、最初にテストを完了したものが返されるようにしたいと考えています。残りの結果を待ちたくありません。
PLINQ でこれを行うことはできますか? それとも、タスクのバッチをスケジュールして、1 つが返されるのを待つ必要がありますか?
ここではおそらく PLINQ では十分ではありません。.NET 4 でを使用することはできますが.First
、これにより順次実行され、目的に反します。(これは .NET 4.5 で改善されることに注意してください。)
ただし、ここでは TPL が正しい答えである可能性が最も高いです。Task<Location>
シリアル ポートごとにを作成し、Task.WaitAnyを使用して、最初の成功した操作を待機できます。
これにより、一連の「タスク」をスケジュールし、最初の結果のみを使用する簡単な方法が提供されます。
過去数日間、これについてオンとオフを考えてきましたが、C# 4.0 でこれを行う組み込みの PLINQ の方法が見つかりません。FirstOrDefaultを使用するというこの質問に対する受け入れられた回答は、完全な PLINQ クエリが完了するまで値を返さず、(順序付けされた) 最初の結果を返します。次の極端な例は、動作を示しています。
var cts = new CancellationTokenSource();
var rnd = new ThreadLocal<Random>(() => new Random());
var q = Enumerable.Range(0, 11).Select(x => x).AsParallel()
.WithCancellation(cts.Token).WithMergeOptions( ParallelMergeOptions.NotBuffered).WithDegreeOfParallelism(10).AsUnordered()
.Where(i => i % 2 == 0 )
.Select( i =>
{
if( i == 0 )
Thread.Sleep(3000);
else
Thread.Sleep(rnd.Value.Next(50, 100));
return string.Format("dat {0}", i).Dump();
});
cts.CancelAfter(5000);
// waits until all results are in, then returns first
q.FirstOrDefault().Dump("result");
利用可能な最初の結果をすぐに取得する組み込みの方法はわかりませんが、2 つの回避策を思いつくことができました。
1 つ目は、タスクを実行するタスクを作成し、タスクを返します。これにより、PLINQ クエリがすばやく完了します。結果のタスクを WaitAny に渡して、最初の結果が利用可能になるとすぐに取得できます。
var cts = new CancellationTokenSource();
var rnd = new ThreadLocal<Random>(() => new Random());
var q = Enumerable.Range(0, 11).Select(x => x).AsParallel()
.WithCancellation(cts.Token).WithMergeOptions( ParallelMergeOptions.NotBuffered).WithDegreeOfParallelism(10).AsUnordered()
.Where(i => i % 2 == 0 )
.Select( i =>
{
return Task.Factory.StartNew(() =>
{
if( i == 0 )
Thread.Sleep(3000);
else
Thread.Sleep(rnd.Value.Next(50, 100));
return string.Format("dat {0}", i).Dump();
});
});
cts.CancelAfter(5000);
// returns as soon as the tasks are created
var ts = q.ToArray();
// wait till the first task finishes
var idx = Task.WaitAny( ts );
ts[idx].Result.Dump("res");
これはおそらくひどい方法です。PLINQ クエリの実際の作業は非常に高速な Task.Factory.StartNew にすぎないため、PLINQ を使用してもまったく意味がありません。シンプルな .Select( i => Task.Factory.StartNew( ...
IEnumerable の方がクリーンで、おそらく高速です。
2 番目の回避策は、キュー (BlockingCollection) を使用し、結果が計算されたらこのキューに挿入するだけです。
var cts = new CancellationTokenSource();
var rnd = new ThreadLocal<Random>(() => new Random());
var q = Enumerable.Range(0, 11).Select(x => x).AsParallel()
.WithCancellation(cts.Token).WithMergeOptions( ParallelMergeOptions.NotBuffered).WithDegreeOfParallelism(10).AsUnordered()
.Where(i => i % 2 == 0 )
.Select( i =>
{
if( i == 0 )
Thread.Sleep(3000);
else
Thread.Sleep(rnd.Value.Next(50, 100));
return string.Format("dat {0}", i).Dump();
});
cts.CancelAfter(5000);
var qu = new BlockingCollection<string>();
// ForAll blocks until PLINQ query is complete
Task.Factory.StartNew(() => q.ForAll( x => qu.Add(x) ));
// get first result asap
qu.Take().Dump("result");
このメソッドでは、作業は PLINQ を使用して行われ、BlockingCollecion の Take() は、PLINQ クエリによって挿入されるとすぐに最初の結果を返します。
これにより望ましい結果が得られますが、より単純な Tasks + WaitAny を使用するよりも利点があるかどうかはわかりません
さらに検討すると、明らかにFirstOrDefault
これを解決するために使用できます。PLINQはデフォルトでは順序を保持せず、バッファリングされていないクエリを使用すると、すぐに戻ります。