在通过 DI 创建的对象的构造函数中使用的 DbContext 的异步问题
Async Issue for DbContext used in constructor of objects created via DI
我想知道是否有人可以阐明什么时候等待什么时候不等待。考虑这段代码
public Task<List<User>> GetUsersForParent(int someParentId)
{
var qry = Context.Users.Where(u=>u.parent = someParentId)
.OrderBy(u=>u.Surname)
return FilterActive(qry);
}
//Actually in a generic base class, but not important (I don't think)
protected Task<List<T>> FilterActive(IQueryable<T> query) where T: BaseEntity
{
return query.Where( q=>q.Active == true ).ToListAsync();
}
然后就是这样使用
var users = await DbHandler.GetUsersForParent(1);
因此等待调用方法,但其他方法不是。这是正确的吗?
是否应该等待调用 ToListAsync() 的方法? (我假设现在正在做这项工作)
我这样做的原因是我发现 DbContext 正被第二个线程使用,出现可怕的异常。我运行没地方看了。我的理解是这些方法正在构建要执行的整个任务,但这会不会扰乱 dbContext?
重新编辑 DbContext 错误
通过 Debug.Print 和 SQL 查询分析缩小了问题的潜在位置(以防万一对其他人有帮助)我可以看到一个正在分析的语句(配置文件中的下一个正在记录异常),我可以通过调试打印看到两种方法 运行。
其中一个方法是 PermissionsManager,它在构造时初始化自身并加载用户数据。这是在通过 DI 框架请求时构建的。
另一种方法是对页面的 OnGet() 方法进行单一查询。 运行通过 ID 获取实体的单个查询,正确等待。
我目前的工作理论是线程 运行 进行 DI 构造和另一个线程 运行 进行页面初始化正在发生冲突。
当我只_person = new Person() // await db.users.get(userid)
设置 PermissionManager 时,问题就消失了。我可以在 2 或 3 次刷新中复制问题 1,尽管刷新页面 30 次以上,但我无法复制该评论。
所以我对 async / await 的真正问题可能更多是关于 DI 注入,并且 运行 在不同的线程上构建?如果是这样,有什么最佳做法可以避免吗?
Should the method calling the ToListAsync() be awaited? (this I assume is now doing the work)
如果您 await
任一方法的内容,您将 return 结果类型,而不是 Task
结果类型,这意味着无法延迟执行。
你的错误将会出现,因为你有多个线程与同一个 DbContext 实例交互,等待或没有这会导致问题,或者你有一些代码调用包含 ToListAsync()
的方法,或另一个不等待的异步 DbContext 操作。
编写 EF 数据访问层 returning Task
相当危险,很容易搬起石头砸自己的脚。
鉴于您的代码结构,我建议进行一些小改动:
public async Task<List<User>> GetUsersForParent(int someParentId)
{
var qry = Context.Users.Where(u=>u.parent = someParentId)
.OrderBy(u=>u.Surname);
qry = FilterActive(qry);
return await qry.ToListAsync();
}
protected IQueryable<T> FilterActive(IQueryable<T> query) where T: BaseEntity
{
return query.Where( q=> q.Active == true );
}
特别是在这里我会避免 returning Task
以减少不当使用和潜在的间歇性错误的风险。 FilterActive 的base-class 方法可以return IQueryable<T>
应用过滤器而不触发操作的执行。这种方式 FilterActive
可以应用,无论你想要一个列表,一个计数,还是简单地做一个存在检查。
总的来说,我建议探索 return IQueryable<TEntity>
而不是 List<TEntity>
等的模式,因为后者要么对性能和灵活性有很多限制,要么需要很多用于处理诸如以下内容的样板代码:
- 正在排序,
- 分页,
- 只得到一个计数,
- 正在执行 Exists 检查,
- 可配置过滤,
- 有选择地预先加载相关数据,或
- 生成高效查询的投影
使用 return List<TEntity>
的方法执行此操作会导致非常复杂的代码以支持上述某些考虑因素,是否应用了这些操作 post - 执行导致比查询更重否则将需要,或者需要大量近乎重复的代码来处理每个场景。
So the calling method is awaited, but the others are not. Is this correct?
我通常建议使用 async
和 await
关键字,以及 only return the tasks directly if the method is extremely simple.
My reason for this is I am getting the DbContext is being used by a second thread dreaded exception. I am running out of places to look. My understanding is the methods are building up the whole task which is executed, but could this be messing with the dbContext?
没有。至少,您发布的代码不会导致该异常。无论是使用 async
/await
关键字,还是直接返回任务,这些方法都是异步的,它们不会尝试一次在 dbcontext 上做不止一件事情。
您的问题可能更上层。 Task.WhenAll
是个很好的搜索工具。
所以构造函数是一个转移注意力的东西。毕竟这是一个缺失的 await,只是不在预期的位置并且代码没有改变。
我找到了罪魁祸首。 basePage 中有一个方法挂接到 MVC 页面的过滤器中。它需要用户并加载他们的权限,但是,由于这种用户权限加载是异步的,因此不会等待此方法(之前不需要它,因为它是同步的)。我将其移至页面生命周期中的异步事件之一,现在一切似乎都很愉快(有合适的等待!)。所以这是一个缺失的等待,但这个故事的寓意是任何时候你将同步方法设为异步,检查到底是什么在使用它!
我想知道是否有人可以阐明什么时候等待什么时候不等待。考虑这段代码
public Task<List<User>> GetUsersForParent(int someParentId)
{
var qry = Context.Users.Where(u=>u.parent = someParentId)
.OrderBy(u=>u.Surname)
return FilterActive(qry);
}
//Actually in a generic base class, but not important (I don't think)
protected Task<List<T>> FilterActive(IQueryable<T> query) where T: BaseEntity
{
return query.Where( q=>q.Active == true ).ToListAsync();
}
然后就是这样使用
var users = await DbHandler.GetUsersForParent(1);
因此等待调用方法,但其他方法不是。这是正确的吗?
是否应该等待调用 ToListAsync() 的方法? (我假设现在正在做这项工作)
我这样做的原因是我发现 DbContext 正被第二个线程使用,出现可怕的异常。我运行没地方看了。我的理解是这些方法正在构建要执行的整个任务,但这会不会扰乱 dbContext?
重新编辑 DbContext 错误
通过 Debug.Print 和 SQL 查询分析缩小了问题的潜在位置(以防万一对其他人有帮助)我可以看到一个正在分析的语句(配置文件中的下一个正在记录异常),我可以通过调试打印看到两种方法 运行。
其中一个方法是 PermissionsManager,它在构造时初始化自身并加载用户数据。这是在通过 DI 框架请求时构建的。
另一种方法是对页面的 OnGet() 方法进行单一查询。 运行通过 ID 获取实体的单个查询,正确等待。
我目前的工作理论是线程 运行 进行 DI 构造和另一个线程 运行 进行页面初始化正在发生冲突。
当我只_person = new Person() // await db.users.get(userid)
设置 PermissionManager 时,问题就消失了。我可以在 2 或 3 次刷新中复制问题 1,尽管刷新页面 30 次以上,但我无法复制该评论。
所以我对 async / await 的真正问题可能更多是关于 DI 注入,并且 运行 在不同的线程上构建?如果是这样,有什么最佳做法可以避免吗?
Should the method calling the ToListAsync() be awaited? (this I assume is now doing the work)
如果您 await
任一方法的内容,您将 return 结果类型,而不是 Task
结果类型,这意味着无法延迟执行。
你的错误将会出现,因为你有多个线程与同一个 DbContext 实例交互,等待或没有这会导致问题,或者你有一些代码调用包含 ToListAsync()
的方法,或另一个不等待的异步 DbContext 操作。
编写 EF 数据访问层 returning Task
相当危险,很容易搬起石头砸自己的脚。
鉴于您的代码结构,我建议进行一些小改动:
public async Task<List<User>> GetUsersForParent(int someParentId)
{
var qry = Context.Users.Where(u=>u.parent = someParentId)
.OrderBy(u=>u.Surname);
qry = FilterActive(qry);
return await qry.ToListAsync();
}
protected IQueryable<T> FilterActive(IQueryable<T> query) where T: BaseEntity
{
return query.Where( q=> q.Active == true );
}
特别是在这里我会避免 returning Task
以减少不当使用和潜在的间歇性错误的风险。 FilterActive 的base-class 方法可以return IQueryable<T>
应用过滤器而不触发操作的执行。这种方式 FilterActive
可以应用,无论你想要一个列表,一个计数,还是简单地做一个存在检查。
总的来说,我建议探索 return IQueryable<TEntity>
而不是 List<TEntity>
等的模式,因为后者要么对性能和灵活性有很多限制,要么需要很多用于处理诸如以下内容的样板代码:
- 正在排序,
- 分页,
- 只得到一个计数,
- 正在执行 Exists 检查,
- 可配置过滤,
- 有选择地预先加载相关数据,或
- 生成高效查询的投影
使用 return List<TEntity>
的方法执行此操作会导致非常复杂的代码以支持上述某些考虑因素,是否应用了这些操作 post - 执行导致比查询更重否则将需要,或者需要大量近乎重复的代码来处理每个场景。
So the calling method is awaited, but the others are not. Is this correct?
我通常建议使用 async
和 await
关键字,以及 only return the tasks directly if the method is extremely simple.
My reason for this is I am getting the DbContext is being used by a second thread dreaded exception. I am running out of places to look. My understanding is the methods are building up the whole task which is executed, but could this be messing with the dbContext?
没有。至少,您发布的代码不会导致该异常。无论是使用 async
/await
关键字,还是直接返回任务,这些方法都是异步的,它们不会尝试一次在 dbcontext 上做不止一件事情。
您的问题可能更上层。 Task.WhenAll
是个很好的搜索工具。
所以构造函数是一个转移注意力的东西。毕竟这是一个缺失的 await,只是不在预期的位置并且代码没有改变。
我找到了罪魁祸首。 basePage 中有一个方法挂接到 MVC 页面的过滤器中。它需要用户并加载他们的权限,但是,由于这种用户权限加载是异步的,因此不会等待此方法(之前不需要它,因为它是同步的)。我将其移至页面生命周期中的异步事件之一,现在一切似乎都很愉快(有合适的等待!)。所以这是一个缺失的等待,但这个故事的寓意是任何时候你将同步方法设为异步,检查到底是什么在使用它!