首页 > 解决方案 > 等待后 EF Core 异步方法挂起/死锁

问题描述

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

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

DSharpPlus 从 IHostedService 异步运行(通过作用域 DI 包装器 - 因为托管服务必须是单例并且 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 代码并将其替换为内存字典。这工作得很好。我什至查看了 DSharpPlus 的代码,并没有发现任何可疑之处。

如果我在某处犯了明显的错误,请多多道歉!这是我第一个使用 async EF 的项目,而且我对 async/await 通常还是比较陌生。如果有帮助的话,我很乐意分享这个项目的完整代码。

提前感谢您的任何帮助!

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

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

标签: c#asynchronous.net-coreasync-awaitentity-framework-core

解决方案


您应该删除.ConfigureAwait(false), ConfigureAwait with false 表示您没有等待任务完成,我真的建议您阅读此博客


推荐阅读