等待后EF核心异步方法挂起/死锁



更新:这个问题实际上与异步无关——我的数据库不同步,LINQ查询失败。DSharpPlus丢弃捕获的异常,因此没有任何问题的迹象。有关更多详细信息,请参阅下面的自我回答。

在.NET 5上尝试使用EF Core的异步方法时遇到挂起/死锁。这是在控制台应用程序中(所以没有ASP.NET、WPF或类似的东西(,但我使用Microsoft.Extensions.Hosting包来管理应用程序生命周期。我还使用了第三方中间件DSharpPlus,它负责将工作调度到线程池中,我在线程池中调用EF。依赖项注入正在使用中,通过Host.CreateDefaultBuilder.进行配置

DSharpPlus是从IHostedService异步运行的(通过作用域DI包装器,因为托管服务必须是singleton,并且DbContext是作用域的(。在从新的DI作用域创建处理程序代码的新实例后,它将事件分派到线程池中(通过Task.Run(。处理程序代码调用一个新创建的注入服务实例,该实例最终调用DbContext(也是一个新实例(。

这比我想要的更复杂,但我所读到的一切都表明它应该有效。不幸的是,当我尝试使用EF时,它会断开。任何使用EF的代码在等待时都会挂起,但当EF被清除时,同样的代码也会工作。这是最短/最简单的事务(为了可读性而减少了一点(:

// This line is from DSharpPlus (not my code) but I'm including it for clarity.
// This line is called at end of a network event handler and exists only to dispatch the event to a thread pool (it immediately returns after this line)
// ExecuteCommandAsync is responsible for creating a new DI scope and fresh instance of the command handler, which contains the function below.
_ = Task.Run(async () => await this.ExecuteCommandAsync(ctx).ConfigureAwait(false));
// Called eventually by above code, inside a fresh object with a new DI scope
// _hydrationLeaderboard, _hydrationOptions, and _logger are injected each time.
// _logger is a standard .NET ILogger instance.
public async Task ScoresCommand(CommandContext ctx)
{
using (_logger.BeginScope($"CmdScores.ScoresCommand@{ctx.Message.Id.ToString()}"))
{
_logger.LogDebug("Requested by [{user}]", ctx.User);

// This never returns
var leaderboard = await _hydrationLeaderboard.GetLeaderboard(_hydrationOptions.LeaderboardSize);

// ** snipped **
}
}
// Called by above code
// _selfcareDb is new DbContext instance from DI
public Task<List<HydrationLeaderboardEntry>> GetLeaderboard(int top = 3)
{
// This never returns. The original has additional LINQ, but it still fails like this too.
// It also fails if method is async/await
return _selfcareDb.UserScores
.Select((us, idx) => new HydrationLeaderboardEntry(
// **Snip**, just copying properties from DB entity to service entity
)
.ToListAsync();
}

我想我已经排除了异步挂起的常见嫌疑——我没有使用.Wait、.GetResult或任何其他类似的调用。我没有任何同步代码(除非我错过了(。我已经确保为每个任务注入一个唯一的DbContext实例。作为测试,我删除了所有EF代码,并用内存中的Dictionary替换它。这很好。我甚至查看了DSharpPlus的代码,没有发现任何可疑之处。

如果我在某个地方犯了明显的错误,我深表歉意这是我第一个使用async EF的项目,我对async/await还比较陌生。如果有帮助的话,我很乐意分享这个项目的完整代码。

提前感谢您的帮助!

编辑:我忘了提一下,我使用的数据库是Sqlite。

更新:Task.Run调用似乎不是问题所在。即使在单个线程中使用完全异步/等待的备用事件调度,对EF的调用仍然挂起。

这是一个DB错误!我把EF迁移搞砸了,所以数据库是空的,这显然引发了一个异常。在这一过程中的某个地方,例外被吞噬并隐藏起来。我修复了数据库错误,现在一切正常。

我还为每个命令处理程序添加了一个简单的try/catch块,现在每当出现错误时,我都会得到输出。

你应该删除.ConfigureAwait(false),用false配置Await意味着你不等待任务完成,我真的建议你阅读这个博客

最新更新