从 Task.WhenAll 使用 DbContext 调用异步方法并返回错误

dbe*_*eon 3 c# concurrency multithreading entity-framework thread-safety

您好,我需要一些关于 Task.WhenAll() 运行的异步方法的建议。它是 dotnet core 3 项目,上下文和服务被添加为 AddScoped。

当我运行 DoWorksAysnc 方法时,我总是遇到一个问题“在上一个操作完成之前,在此上下文上启动了第二个操作。这通常是由使用同一 DbContext 实例的不同线程引起的。有关如何避免 DbContext 线程问题的更多信息”

我知道 DbContext 不是线程安全的。但我是否可以同时运行 DowksAsync 方法..?

谢谢,

   public async Task DoWorksAsync(List<int> ids)
        {
            
            if (ids.Count > 0)
            {
                var listOfTasks = new List<Task>();
                foreach(var id in ids)
                {
                    var task = Task.Run(async () => await UpdateDataById(id));
                    listOfTasks.Add(task);
                }
                await Task.WhenAll(listOfTasks);
            }
        }
Run Code Online (Sandbox Code Playgroud)

UpdateDataById 方法是

 public async Task UpdateDataById (int id)
        {
            try
            {

                //Get products from webservice
                List<Product> products = await _searchService.GetItemsByIdAsync(id);
                await _dataService.CreateTableIfNotExist(id); //Method inside is await _dbContext.Database.ExecuteSqlRawAsync(string.Format(_sqlCreateTableIfNotExist, id));


                // UpdateProductsToDataTable method includes truncate table and bulkcopy.writeToServerAsync(tablename)
                //await _dbContext.Database.ExecuteSqlRawAsync(string.Format("Truncate Table {0}", tableName));    
                //await bulkcopy.WriteToServerAsync();
              
                if (await _dataService.UpdateProductsToDataTable(id,products) == true) 
                {
                   await _emailService.sendEmailNotificationAsync();
                }


                _logService.AddLog("process is done"); 

                _unitOfWork.SaveAsync(); //_context.SaveChangesAsync()
            }
            catch (Exception ex)
            {
                await _logService.AddErrorLog(new ErrorLog
                {
                    Id= id,
                    ErrorMessage = ex.Message
                });
                _unitOfWork.SaveAsync();
            }
        }
Run Code Online (Sandbox Code Playgroud)

AAA*_*ddd 7

尽管您知道这一点,但DbContexts它们不是线程安全的,故事结束了..

这个问题很常见,如果您遵循以下经验法则,此修复很简单

  1. 不要缓存 a DbContext,它们已经在内部缓存了。如果您有一个存储库(我真的建议您不应该这样做),则永远不要将其DbContext作为实例成员,除非您绝对确定它是线程安全的并且它正在非常谨慎地用于小型工作负载。但请参阅第 2 点。
  2. DbContext如果可以的话,总是在 using 语句中添加一个
  3. 切勿从多个线程调用a 的实例DbContext(它们不是线程安全的)。分别参见第1点和第2点
  4. 如果您发现自己想要在数据库中投入更多线程(以加快速度),那么您可能做错了什么。数据库并行其自己的工作负载和查询计划,并且通常可以比您做得更好(在合理范围内)。编写更好的查询并分析它们。即不要单独查询每个 id,而是一次查询所有 id(如果可以的话)

如果您确实需要这样做,请DbContext通过一种或另一种方式确保每个线程都是一个单独的实例。

或者按照Jeremy Lakeman的建议。确保每个方法调用的上下文都是唯一的。