Entity Framework が使用している基になるデータベース接続は、スレッドセーフではありません。実行する別のスレッドで操作ごとに新しいコンテキストを作成する必要があります。
操作を並列化する方法についての懸念は有効です。多くのコンテキストは、開いたり閉じたりするのにコストがかかります。
代わりに、コードの並列化についての考え方を逆にしたい場合があります。多数のアイテムをループしてから、アイテムごとにストアド プロシージャを順番に呼び出しているようです。
可能であれば、プロシージャごとに新しいTask<TResult>
(またはTask
、結果が必要ない場合は ) を作成し、その中で1 つのコンテキストを開き、すべての項目をループしてから、ストアド プロシージャを実行します。この方法では、並行して実行しているストアド プロシージャの数と同じ数のコンテキストしかありません。Task<TResult>
MyDbContext
と の2 つのストアド プロシージャを持つ がDoSomething1
ありDoSomething2
、どちらもクラスのインスタンスを取ると仮定しますMyItem
。
上記を実装すると、次のようになります。
// You'd probably want to materialize this into an IList<T> to avoid
// warnings about multiple iterations of an IEnumerable<T>.
// You definitely *don't* want this to be an IQueryable<T>
// returned from a context.
IEnumerable<MyItem> items = ...;
// The first stored procedure is called here.
Task t1 = Task.Run(() => {
// Create the context.
using (var ctx = new MyDbContext())
// Cycle through each item.
foreach (MyItem item in items)
{
// Call the first stored procedure.
// You'd of course, have to do something with item here.
ctx.DoSomething1(item);
}
});
// The second stored procedure is called here.
Task t2 = Task.Run(() => {
// Create the context.
using (var ctx = new MyDbContext())
// Cycle through each item.
foreach (MyItem item in items)
{
// Call the first stored procedure.
// You'd of course, have to do something with item here.
ctx.DoSomething2(item);
}
});
// Do something when both of the tasks are done.
ストアド プロシージャを並列に実行できない場合(それぞれが特定の順序で実行されることに依存しています)、操作を並列化することはできますが、それはもう少し複雑です。
アイテム全体にカスタム パーティションを作成する方法を検討します ( classの静的Create
メソッドを使用)。これにより、実装を取得する手段が得られます (注意してください。これはそうではないため、それを超えることはできません)。Partitioner
IEnumerator<T>
IEnumerable<T>
foreach
返されたインスタンスごとIEnumerator<T>
に新しいインスタンスを作成しTask<TResult>
(結果が必要な場合)、本体でコンテキストを作成し、ストアド プロシージャを順番に呼び出して、Task<TResult>
によって返されたアイテムを循環します。IEnumerator<T>
それは次のようになります。
// Get the partitioner.
OrdinalPartitioner<MyItem> partitioner = Partitioner.Create(items);
// Get the partitions.
// You'll have to set the parameter for the number of partitions here.
// See the link for creating custom partitions for more
// creation strategies.
IList<IEnumerator<MyItem>> paritions = partitioner.GetPartitions(
Environment.ProcessorCount);
// Create a task for each partition.
Task[] tasks = partitions.Select(p => Task.Run(() => {
// Create the context.
using (var ctx = new MyDbContext())
// Remember, the IEnumerator<T> implementation
// might implement IDisposable.
using (p)
// While there are items in p.
while (p.MoveNext())
{
// Get the current item.
MyItem current = p.Current;
// Call the stored procedures. Process the item
ctx.DoSomething1(current);
ctx.DoSomething2(current);
}
})).
// ToArray is needed (or something to materialize the list) to
// avoid deferred execution.
ToArray();