将实体框架与任务并行库结合使用

Fik*_*man 1 c# sql-server multithreading entity-framework task-parallel-library

我有一个使用 .NET 4.0 和 EF 6.0 开发的应用程序。程序的前提相当简单。观察文件系统上的特定文件夹。当一个新文件放入此文件夹时,在 SQL Server 数据库中查找有关此文件的信息(使用 EF),然后根据找到的内容将文件移动到文件系统上的另一个文件夹。文件移动完成后,返回数据库并更新有关此文件的信息(注册文件移动)。

这些是大型媒体文件,因此每个文件都可能需要一段时间才能移动到目标位置。此外,我们可能会使用数百个位于源文件夹中的媒体文件来启动此服务,这些媒体文件需要分派到目标位置。

所以为了加快速度,我开始使用任务并行库(异步/等待不可用,因为这是 .NET 4.0)。对于源文件夹中的每个文件,我在数据库中查找有关它的信息,确定它需要移动到哪个目标文件夹,然后开始一个开始移动文件的新任务......

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…
                         .......
                         }
                      }
Run Code Online (Sandbox Code Playgroud)

现在,一旦此任务完成,我必须返回数据库并更新有关文件的信息。这就是我遇到问题的地方。(请记住,我可能有几个“移动文件任务”并行运行,它们将在不同的时间完成。目前,我正在使用任务继续在数据库中注册文件移动:

filemoveTask.ContinueWith(
                       t =>
                       {
                           if (t.IsCompleted && t.Result)
                           {
                             RegisterFileMoveinDB();
                           }
                       }
Run Code Online (Sandbox Code Playgroud)

问题是我使用相同的数据库上下文在主任务中以及稍后在嵌套任务上执行的 RegistetrFilemoveinDB() 方法中查找文件信息。在将多个文件移动到一起时,我收到了各种奇怪的异常(主要是关于 SQL 服务器数据读取器等)。在线搜索答案显示,像我在这里做的几个任务之间共享数据库上下文是一个很大的禁忌,因为 EF 不是线程安全的。

我宁愿不为每个文件移动创建一个新的数据库上下文,因为可能有数十个甚至数百个文件同时运行。什么是好的替代方法?当嵌套任务完成并完成主任务中的文件移动注册时,有没有办法“通知”主任务?或者我是否以错误的方式处理这个问题,并且有更好的方法来解决这个问题?

Moh*_*oho 5

最好的办法是DbContext为每个线程确定范围。 Parallel.ForEach具有对此有用的重载(具有Func<TLocal> 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();
    } );
Run Code Online (Sandbox Code Playgroud)

SaveChanges()如果您希望尽快更新数据库,您可以在正文表达式/RegisterFileMoveInDB 中调用。我建议将文件系统操作与数据库事务联系起来,这样如果数据库更新失败,文件系统操作就会回滚。