Entity Framework 使用的底层数据库连接不是线程安全的。您将需要为要执行的另一个线程上的每个操作创建一个新上下文。
您对如何并行化操作的担忧是有道理的;许多上下文的打开和关闭成本很高。
相反,您可能想要颠倒您对并行化代码的想法。似乎您正在循环多个项目,然后为每个项目串行调用存储过程。
如果可以,为每个过程Task<TResult>
创建一个新的(或者Task
,如果您不需要结果),然后在其中打开一个上下文,遍历所有项目,然后执行存储过程。这样,您只有与并行运行的存储过程数量相等的上下文数量。Task<TResult>
假设您有一个MyDbContext
带有两个存储过程的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();