【发布时间】:2021-05-29 00:08:31
【问题描述】:
我的项目是多线程的。我正在使用 EF Core。有时我会收到错误:
System.InvalidOperationException: '在前一个操作完成之前,在此上下文上启动了第二个操作。这通常是由不同的线程同时使用同一个 DbContext 实例引起的。有关如何避免 DbContext 线程问题的更多信息
我研究了这个问题,发现了一些关于它的注释:
- EF Core 不是安全线程
- 最好的解决方案是在每个线程中创建新的上下文
但有时我需要在多个线程中使用相同的上下文。我重现了这个问题。 我的模型:
public class School
{
public Guid Guid { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
public class Student
{
public Guid Guid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DOB { get; set; }
public Guid SchoolGuid { get; set; }
public virtual School School { get; set; }
public virtual ICollection<Backpack> Backpacks { get; set; }
}
public class Backpack
{
public Guid Guid { get; set; }
public string Name { get; set; }
public decimal Cost { get; set; }
public Guid StudentGuid { get; set; }
public virtual Student Student { get; set; }
}
重现步骤:
static void Main(string[] args)
{
MainContext mainContext = new MainContext();
CreateData(mainContext);
var task1 = Task.Run(new Action(() => GetStudents(mainContext)));
var task2 = Task.Run(new Action(() => GetBackpacks(mainContext)));
Task.WaitAll(task1, task2);
}
static void GetStudents(MainContext mainContext)
{
var students = mainContext.Set<Student>().ToList();
}
static void GetBackpacks(MainContext mainContext)
{
var backpacks = mainContext.Set<Backpack>().ToList();
}
static void CreateData(MainContext mainContext)
{
if (!mainContext.Set<School>().Any())
{
for (int i = 0; i < 10; i++)
{
var school = new School() { Guid = Guid.NewGuid(), Address = $"Scho olAddress{i}", Name = $"SchoolName{i}" };
mainContext.Add(school);
for (int j = 0; j < 1000; j++)
{
var student = new Student() { Guid = Guid.NewGuid(), LastName = $"LastName{j}", FirstName = $"FirstName{j}", DOB = DateTime.Today, School = school };
mainContext.Add(student);
for (int l = 0; l < 100; l++)
{
var backpack = new Backpack() { Guid = Guid.NewGuid(), Name = $"Name{i}", Student = student, Cost = l };
mainContext.Add(backpack);
}
}
}
mainContext.SaveChanges();
}
}
我希望第二次手术等到完成第一次手术。我该如何配置?我可能有很多线程可以使用相同的上下文。如何自动建立使用上下文的队列?
使用锁对某些用例有帮助,但并非对所有用例都有帮助:
static void GetStudents(MainContext mainContext)
{
lock (mainContext)
{
Console.WriteLine("Loading Students are started");
var students = mainContext.GetEntities<Student>().ToList();
Console.WriteLine("Students are loaded");
}
}
static void GetBackpacks(MainContext mainContext)
{
lock (mainContext)
{
Console.WriteLine("Loading Backpakcs are started");
var backpacks = mainContext.GetEntities<Backpack>().ToList();
Console.WriteLine("Backpakcs are loaded");
}
}
【问题讨论】:
-
使其异步并等待结果。检查是否成功。如果是,请继续下一步
-
DbContext 是一个工作单元,而不是数据库连接。它并不意味着长期存在或重复使用。它不能从多个线程中使用,这样做也没有意义。该错误在您的
Main方法中。GetStudents和GetBackpacks方法也没什么意义 - 要从异步执行中获得任何好处,您需要使用ToListAsync,而不是启动另一个线程并使用ToList()阻止它 -
最后,代码在左右创建新的 DbSet。没有教程这样做是有原因的。 DbContext 不是连接,它是特定实体的工作单元,通过 DbSet 存储库属性公开。充其量,这会使代码更难阅读。由于每个
Backpack都有一个Student,因此您可以使用myContext.Backpacks.Include(x=>x.Student)一次加载所有相关对象。 -
@PanagiotisKanavos 这些方法只是为了重现我的问题。主要问题是可能有两个线程请求一个上下文。我无法跟进所有线程。我想在全球范围内修复它,例如: GetEntities() { if(context.IsUsing) wait().ContinueWith()....; }
-
“有时我需要在多个线程中使用相同的上下文”不,你真的不需要。您的数据库可以处理多个并发请求,但它们只需要来自不同的 DbContext 实例。
标签: c# multithreading entity-framework-core