我正在将 C# ASP.NET Core 7 项目从使用带有常规 SQL 查询的
SqlClient
改为使用 Entity Framework。当应用程序运行多个长时间运行的任务时,我有一个特殊的地方,它是一种带有大 for 循环的模拟,用户可以在其中跟踪进度,因此,每个任务在其自己的任务中数十次写入数据库.旧的 SqlClient
解决方案以最少的 CPU 和内存使用率顺利运行,但使用 EF,一旦线程开始工作,一切都会停止并冻结。
我知道
DbContext
不是线程安全的,因此每个任务都创建自己的 DbContext
,他们创建它,特别是在发生数据库插入的地方,一旦不需要它们,我会立即处理它们,但是,在 for 循环中,它完全冻结了计算机,一切都停止了。 Web 应用程序甚至不再响应。
简化的控制器:
public SmContext db { get; set; }
public SimulateRoundModel(SmContext db)
{
this.db = db;
}
public async Task<IActionResult> OnPost()
{
List<Match> matches = new CollectorClass(db).Collect();
MyClass.Wrapper(matches);
return Page();
}
简化代码:
public static void Wrapper(List<Match> matches)
{
Parallel.For(0, matches.Count,
index =>
{
matches[index].LongSim();
});
}
比赛类:
private SmContext db { get; set; }
public Match(db)
{
this.db = db;
}
public void longSim()
{
db.Dispose(); // disposing the main dbcontext that the constructor receives, we don't want to use that
using (SmContext db = new SmContext())
{
// some initial query and insert
}
for (int i = 0; i < 100; i++)
{
Thread.Sleep(5000);
// some simulation
db = new SmContext();
SomeInsert(); // these are using the db for the insert
SomeInsert();
SomeInsert();
db.Dispose();
}
}
我们谈论的是 5-50 场比赛,并且
Parallel.For
使用旧的 SqlClient
解决方案对它们进行了很好的优化,我之前看到过 200 场比赛没有问题。这些不是密集的任务,只是简单的东西和一些查询,但它们运行时间很长。理想情况下,我希望在不进行重大重写的情况下继续将进度保存到数据库中。
最终的问题是,这里是否存在概念问题,我是新手无法识别,或者这个解决方案应该可以正常工作并且代码的黑点中出现了一些模糊的地方?
更多的是猜测领域,然后是我可以证明的东西,但根据我的经验,具有相同上下文的多个
SomeInsert
看起来有点可疑。 EF Core 执行插入/更新操作依赖于 tracking 并且即使你使用 AsNoTracking
新的条目仍然会被 change tracker 处理,所以如果你实际上插入了很多数据(并且注意 EF 总是不太合适对于批量插入),您最终会得到具有大量实体的更改跟踪器,这些实体会大大降低 EF 性能。我会建议以下选项之一:
ChangeTracker.Clear
*(这也可以用来代替在循环外重新创建上下文)EFCore.BulkExtensions
)支持批量插入* - 您将需要确定插入数据的最佳大小以重新创建/清除跟踪器并调用
SaveChanges
,就像在此 answer 中对 EF 的旧迭代所做的那样。
附言
Parallel.For
public void longSim()
Thread.Sleep(5000);
我强烈建议使用
longSim
使 await Task.Delay(5000)
异步并切换到支持异步方法的 Parallel.ForEachAsync
。这也将允许使用 EF Core 方法的异步版本。