从 Task.WhenAll 调用的异步方法使用 DbContext 并返回错误
Posted
技术标签:
【中文标题】从 Task.WhenAll 调用的异步方法使用 DbContext 并返回错误【英文标题】:Async method called from Task.WhenAll use DbContext and returned an error 【发布时间】:2021-03-06 21:18:44 【问题描述】:您好,我需要一些关于 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);
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();
【问题讨论】:
对你为什么 Task.Run 一个等待已经返回一个任务的方法的异步 lambda 感到困惑。如果你想要一个任务,直接调用 UpdateDataById 会给你一个任务? 【参考方案1】:虽然你知道这一点,DbContexts
不是线程安全的,故事结束..
此问题很常见,如果您遵循这些经验法则
,此修复很简单-
不要缓存
DbContext
,它们已经在内部缓存。如果您有一个存储库(我真的建议您不要),除非您绝对确定它是 线程安全 并且它被非常谨慎地用于小型工作负载。但是请参阅第 2 点。
总是如果可以的话,在 using 语句中添加 DbContext
。
从不调用来自多个线程的DbContext
实例(它们不是线程安全的)。 分别参见第 1 点和第 2 点
如果您发现自己想在数据库中添加更多线程(以加快速度),那么您可能做错了。数据库并行处理自己的工作负载和查询计划,并且通常可以比您做得更好(在合理范围内)。编写更好的查询并分析它们。 IE。不要单独查询每个 id,而是一次查询所有 id(如果可以的话)
如果您确实需要这样做,请确保您的 DbContext
通过一种或另一种方式为每个线程提供一个单独的实例。
或按照 Jeremy Lakeman 的建议。确保每个方法调用的上下文都是唯一的。
【讨论】:
另外,您可以为每个方法调用创建一个服务范围和上下文。 @JeremyLakeman,更新和归因。如果您想随时更新我的答案,如果您想通过另一个答案在 DI 路径上走得更远,我会很高兴地投票 嗨,它是 dotnet core 3 项目,上下文和服务已添加为 AddScoped。我想进行 1 个方法调用,但要更新多个 id 的数据。有什么想法吗? 见第 1 点。这些最简单的方法来实现这一点,只是在方法中启动上下文(如果可以的话),将其放在 using 语句中。但是还有其他解决方案 扩展点 1:DbContext 本身充当存储库,因此不建议实现存储库模式并与 DbContext 一起使用。【参考方案2】:“使用 Entity Framework 6 以正确的方式管理 DbContext:深入指南”是理解 DbContext 问题的好论文。 link
一些亮点:
环境 DbContext 范围按预期流经代码执行 以简单、干净的方式管理 DbContext 实例的生命周期 自动管理多个 DbContext 派生类型 支持嵌套范围和异步代码 DbContext 生命周期不再依赖 DI 根据需要设置 DbContext 范围:JoinExisting、CreateNew、Suppress JoinExisting:作用域生命周期;最常见的服务/API 方法 CreateNew:瞬态生命周期;最常见的异步代码 抑制:抑制环境上下文;最常见的并行代码 DbContextScope 基于 EF 中的 TransactionScope 类 .NET 5/6 存在分叉:https://github.com/yegor-mialyk/DbContextScope【讨论】:
以上是关于从 Task.WhenAll 调用的异步方法使用 DbContext 并返回错误的主要内容,如果未能解决你的问题,请参考以下文章
异步/等待死锁 Task.WaitAll 与 Task.WhenAll [重复]
Task CancellationTokenSource和Task.WhenAll的应用
可以使用 Task.WhenAll(...) 或其他比每次等待更有意义的东西重写此异步/等待代码吗? [复制]