.NET 4.0 以降では、さらに 2 つのオプション (および IMO のクリーンなオプション) を利用できます。
CountdownEvent
1 つ目は、クラスを使用することです。これにより、インクリメントとデクリメントを自分で処理する必要がなくなります。
int tasks = <however many tasks you're performing>;
// Dispose when done.
using (var e = new CountdownEvent(tasks))
{
// Queue work.
ThreadPool.QueueUserWorkItem(() => {
// Do work
...
// Signal when done.
e.Signal();
});
// Wait till the countdown reaches zero.
e.Wait();
}
ただし、さらに堅牢なソリューションがあり、それは次のようにTask
classを使用することです。
// The source of your work items, create a sequence of Task instances.
Task[] tasks = Enumerable.Range(0, 100).Select(i =>
// Create task here.
Task.Factory.StartNew(() => {
// Do work.
}
// No signalling, no anything.
).ToArray();
// Wait on all the tasks.
Task.WaitAll(tasks);
Task
クラスと呼び出しを使用するとWaitAll
、コード全体でスレッド化プリミティブが少なくなるため、IMO ははるかにクリーンになります (注意、待機ハンドルはありません)。カウンターを設定したり、インクリメント/デクリメントを処理したりする必要はありません。タスクを設定してから待つだけです。これにより、コードは何をしたいのかという基本的な方法ではなく、より表現力豊かになります(少なくとも、並列化を管理するという点では)。
.NET 4.5 にはさらに多くのオプションが用意されており、クラスで静的メソッドをTask
呼び出すことにより、一連のインスタンスの生成を簡素化できます。Run
Task
// The source of your work items, create a sequence of Task instances.
Task[] tasks = Enumerable.Range(0, 100).Select(i =>
// Create task here.
Task.Run(() => {
// Do work.
})
// No signalling, no anything.
).ToArray();
// Wait on all the tasks.
Tasks.WaitAll(tasks);
または、TPL DataFlow ライブラリ(System
名前空間にあるため、Entity Framework のように NuGet からのダウンロードであっても公式です) を利用してActionBlock<TInput>
、次のようにを使用できます。
// Create the action block. Since there's not a non-generic
// version, make it object, and pass null to signal, or
// make T the type that takes the input to the action
// and pass that.
var actionBlock = new ActionBlock<object>(o => {
// Do work.
});
// Post 100 times.
foreach (int i in Enumerable.Range(0, 100)) actionBlock.Post(null);
// Signal complete, this doesn't actually stop
// the block, but says that everything is done when the currently
// posted items are completed.
actionBlock.Complete();
// Wait for everything to complete, the Completion property
// exposes a Task which can be waited on.
actionBlock.Completion.Wait();
はデフォルトで一度に 1 つの項目を処理することに注意してくださいActionBlock<TInput>
。一度に複数のアクションを処理する場合は、ExecutionDataflowBlockOptions
インスタンスを渡してMaxDegreeOfParallelism
プロパティを設定することにより、コンストラクターで処理する並行項目の数を設定する必要があります。 :
var actionBlock = new ActionBlock<object>(o => {
// Do work.
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
アクションが真にスレッド セーフである場合は、MaxDegreeOfParallelsim
プロパティをDataFlowBlockOptions.Unbounded
次のように設定できます。
var actionBlock = new ActionBlock<object>(o => {
// Do work.
}, new ExecutionDataflowBlockOptions {
MaxDegreeOfParallelism = DataFlowBlockOptions.Unbounded
});
ポイントは、オプションをどの程度並列にするかを細かく制御できることです。
もちろん、インスタンスに渡したい一連の項目がある場合は、次のように実装をActionBlock<TInput>
リンクしISourceBlock<TOutput>
て にフィードできます。ActionBlock<TInput>
// The buffer block.
var buffer = new BufferBlock<int>();
// Create the action block. Since there's not a non-generic
// version, make it object, and pass null to signal, or
// make T the type that takes the input to the action
// and pass that.
var actionBlock = new ActionBlock<int>(o => {
// Do work.
});
// Link the action block to the buffer block.
// NOTE: An IDisposable is returned here, you might want to dispose
// of it, although not totally necessary if everything works, but
// still, good housekeeping.
using (link = buffer.LinkTo(actionBlock,
// Want to propagate completion state to the action block.
new DataflowLinkOptions {
PropagateCompletion = true,
},
// Can filter on items flowing through if you want.
i => true)
{
// Post 100 times to the *buffer*
foreach (int i in Enumerable.Range(0, 100)) buffer.Post(i);
// Signal complete, this doesn't actually stop
// the block, but says that everything is done when the currently
// posted items are completed.
actionBlock.Complete();
// Wait for everything to complete, the Completion property
// exposes a Task which can be waited on.
actionBlock.Completion.Wait();
}
何をする必要があるかに応じて、TPL Dataflow ライブラリは、リンクされたすべてのタスクの同時実行を処理し、各部分をどの程度並列にするかを非常に具体的に指定できるという点で、はるかに魅力的なオプションになります。 、各ブロックの関心の適切な分離を維持しながら。