C# 结合任务并行库使用实体框架
我有一个应用程序,我们正在使用.NET4.0和EF6.0开发。这个程序的前提很简单。查看文件系统上的特定文件夹。将新文件放入此文件夹时,请在SQL Server数据库中查找有关此文件的信息(使用EF),然后根据找到的信息,将文件移动到文件系统上的另一个文件夹。文件移动完成后,返回数据库并更新有关此文件的信息(注册文件移动) 这些是大型媒体文件,因此可能需要一段时间才能移动到目标位置。此外,我们可能会启动此服务,因为源文件夹中已有数百个这样的媒体文件,需要发送到目标位置 为了加快速度,我开始使用任务并行库(async/await不可用,因为这是.NET4.0)。对于源文件夹中的每个文件,我在DB中查找有关它的信息,确定它需要移动到哪个目标文件夹,然后启动一个新任务,开始移动该文件C# 结合任务并行库使用实体框架,c#,sql-server,multithreading,entity-framework,task-parallel-library,C#,Sql Server,Multithreading,Entity Framework,Task Parallel Library,我有一个应用程序,我们正在使用.NET4.0和EF6.0开发。这个程序的前提很简单。查看文件系统上的特定文件夹。将新文件放入此文件夹时,请在SQL Server数据库中查找有关此文件的信息(使用EF),然后根据找到的信息,将文件移动到文件系统上的另一个文件夹。文件移动完成后,返回数据库并更新有关此文件的信息(注册文件移动) 这些是大型媒体文件,因此可能需要一段时间才能移动到目标位置。此外,我们可能会启动此服务,因为源文件夹中已有数百个这样的媒体文件,需要发送到目标位置 为了加快速度,我开始使用任
LookupFileinfoinDB(filename)
{
// use EF DB Context to look up file in DB
}
// start a new task to begin the file move
var moveFileTask = Task<bool>.Factory.StartNew(
() =>
{
var success = false;
try
{
// the code to actually moves the file goes here…
.......
}
}
问题是,我使用相同的DB上下文在主任务中以及稍后在嵌套任务上执行的RegisterFileMoveInDB()方法中查找文件信息。在移动多个文件时,我遇到了各种奇怪的异常(主要是关于SQL server Data reader等)。在线搜索答案发现,像我在这里做的几个任务之间共享DB上下文是一个很大的禁忌,因为EF不是线程安全的
我不希望为每个文件移动创建一个新的DB上下文,因为可能有几十个甚至数百个文件同时移动。什么是好的替代方法?当嵌套任务完成并在主任务中完成文件移动注册时,是否有方法“通知”主任务?还是我以错误的方式处理这个问题,并且有更好的方法来解决这个问题?您最好的选择是为每个线程定义
DbContext
Parallel.ForEach
具有对此有用的重载(带有Func initLocal
的重载:
Parallel.ForEach(
fileNames, // the filenames IEnumerable<string> to be processed
() => new YourDbContext(), // Func<TLocal> localInit
( fileName, parallelLoopState, dbContext ) => // body
{
// your logic goes here
// LookUpFileInfoInDB( dbContext, fileName )
// MoveFile( ... )
// RegisterFileMoveInDB( dbContext, ... )
// pass dbContext along to the next iteration
return dbContext;
}
( dbContext ) => // Action<TLocal> localFinally
{
dbContext.SaveChanges(); // single SaveChanges call for each thread
dbContext.Dispose();
} );
Parallel.ForEach(
filename,//要处理的文件名IEnumerable
()=>新建YourDbContext(),//Func localini
(文件名,parallelLoopState,dbContext)=>//正文
{
//你的逻辑是这样的
//LookUpFileInfoInDB(dbContext,文件名)
//移动文件(…)
//RegisterFileMoveInDB(dbContext,…)
//将dbContext传递给下一个迭代
返回dbContext;
}
(dbContext)=>//操作localFinally
{
dbContext.SaveChanges();//为每个线程调用单个SaveChanges
dbContext.Dispose();
} );
您可以调用
SaveChanges()
在主体表达式/RegisterFileMoveInDB中,如果您希望尽快更新数据库。我建议将文件系统操作与数据库事务绑定,以便在数据库更新失败时回滚文件系统操作。您的最佳选择是为每个线程确定DbContext
的范围。Parallel.ForEach
具有对此有用的重载(带有Func initLocal的重载
:
Parallel.ForEach(
fileNames, // the filenames IEnumerable<string> to be processed
() => new YourDbContext(), // Func<TLocal> localInit
( fileName, parallelLoopState, dbContext ) => // body
{
// your logic goes here
// LookUpFileInfoInDB( dbContext, fileName )
// MoveFile( ... )
// RegisterFileMoveInDB( dbContext, ... )
// pass dbContext along to the next iteration
return dbContext;
}
( dbContext ) => // Action<TLocal> localFinally
{
dbContext.SaveChanges(); // single SaveChanges call for each thread
dbContext.Dispose();
} );
Parallel.ForEach(
filename,//要处理的文件名IEnumerable
()=>新建YourDbContext(),//Func localini
(文件名,parallelLoopState,dbContext)=>//正文
{
//你的逻辑是这样的
//LookUpFileInfoInDB(dbContext,文件名)
//移动文件(…)
//RegisterFileMoveInDB(dbContext,…)
//将dbContext传递给下一个迭代
返回dbContext;
}
(dbContext)=>//操作localFinally
{
dbContext.SaveChanges();//为每个线程调用单个SaveChanges
dbContext.Dispose();
} );
如果希望尽快更新数据库,可以在主体表达式/RegisterFileMoveInDB中调用
SaveChanges()
。我建议将文件系统操作与数据库事务绑定,以便在数据库更新失败时回滚文件系统操作。根据@Moho问题:
IO异步
操作中的线程取自
NET运行时CLR的线程池,因此它是一种非常有效的机制
你自己创建线程,你用旧的方式创建线程
效率低下,尤其是对于IO操作async
时,您不必立即等待。将等待推迟到必要时问候。根据@Moho问题:
IO异步
操作中的线程取自
NET运行时CLR的线程池,因此它是一种非常有效的机制
你自己创建线程,你用旧的方式创建线程
效率低下,尤其是对于IO操作async
时,您不必立即等待。将等待推迟到必要时TaskScheduler exclusiveScheduler
= new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler;
//...
filemoveTask.ContinueWith(t =>
{
if (t.Result)
{
RegisterFileMoveinDB();
}
}, exclusiveScheduler);
您还可以将实例的作为的参数传递。这样,continuations将按顺序运行,而不是同时运行
TaskScheduler exclusiveScheduler
= new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler;
//...
filemoveTask.ContinueWith(t =>
{
if (t.Result)
{
RegisterFileMoveinDB();
}
}, exclusiveScheduler);
我只想在RegisterFileMoveinDB和LookupFileinfoinDB中定义单独的DbContext对象。您使用的是外部资源(文件系统、数据库)-因此
async wait
比“浪费”更适合您的情况用于IO操作的线程。您可以在.NET 4.0中使用async wait
。@Fabio-线程是如何被浪费的?当您调用可等待的xyzAsync(…)
方法时,您认为会发生什么?@Moho,执行IO操作的线程不做任何事情-只等待