首页 > 解决方案 > 以线程安全的方式使用 DbContext 进行异步搜索

问题描述

我将 EFCore 5 与 Azure SQL Server 和 C#/WPF 前端一起使用。我正在使用 Fody 来触发我的 PropertyChanged 事件。

我有一个自定义实时搜索控件,其中包含一个包含搜索字符串的文本框和一个显示结果的列表框。

TextBox.Text 绑定到字符串属性“SearchString”。我订阅了 PropertyChanged,当它被触发时,我调用我的存储库类来执行异步数据库搜索(实际上在查询中我还有一些 .Where 和 .Include 子句,但已经提取了相关部分):

result = await Context.Where(p => (p.Surname == surname)
    .OrderBy(p => p.Surname)
    .Take(maxResults / sString.Length).ToListAsync();

return result;

然后,我将生成的列表放入同一个类中的一个属性中,该属性绑定到 ListBox.ItemsSource 属性。到现在为止,这同步运行良好(但缓慢) - 我现在只是切换到异步。

我的问题是,尽管“等待”每个对数据库的异步调用,但如果我输入得太快,我会在 DbContext 类上得到“在前一个操作完成之前在此上下文上启动了第二个操作”。

我最好的猜测是,因为我对 DB Repository 的搜索功能的初始调用包含在一个事件中,并且该事件在 UI 线程中触发,所以它“覆盖”了我正在使用 await 并导致第二个查询运行的事实. 我要么需要一种停止第一个查询的方法,要么需要一种确保等待得到尊重的方法。

有什么建议可以让 ToListAsync() 很好地工作,但允许 UI 感觉响应?

标签: c#multithreadingazureasync-awaitentity-framework-core

解决方案


我订阅了 PropertyChanged,当它被触发时,我调用我的存储库类来执行异步数据库搜索......尽管“等待”每次对数据库的异步调用,如果我输入得太快,我会得到“第二个操作已启动DbContext 类上的前一个操作完成之前的此上下文。

我最好的猜测是,因为我对 DB 存储库的搜索功能的初始调用包含在一个事件中,并且该事件在 UI 线程中触发,所以它“覆盖”了我正在使用 await 并导致第二个查询运行的事实. 我要么需要一种停止第一个查询的方法,要么需要一种确保等待得到尊重的方法。

这是问题async void之一:调用代码很难知道异步方法何时完成。这async void PropertyChanged实际上是导致问题的原因。

有什么建议可以让 ToListAsync() 很好地工作,但允许 UI 感觉响应?

首先,我建议像其他人评论的那样去抖动。这将减少不必要的数据库调用,尽管它不会解决这个问题。对于正确的修复,您应该有一个数据库上下文池,您的代码从中分配(并在操作完成后返回),或者只使用SemaphoreSlim. 如果您有一个共享SemaphoreSlim的(与您现有的位置相同的位置Context),并await semaphore.WaitAsync();在 db 操作之前和 db 操作semaphore.Release()之后调用,则可以确保一次访问:

await ContextMutex.WaitAsync();
try
{
  result = await Context.Where(p => (p.Surname == surname)
      .OrderBy(p => p.Surname)
      .Take(maxResults / sString.Length).ToListAsync();
}
finally
{
  ContextMutex.Release();
}

我现在只是切换到异步。

它的一个很好的特性SemaphoreSlim是它也适用于同步代码;您可以使用该代码调用Wait而不是await WaitAsync. 您应该对 的所有访问应用互斥Context以确保正确性。


推荐阅读