使用EF Core时,异步调用比同步调用慢。

4
有没有想法为什么这段代码需要 49 毫秒?
public void OnGet(String sessionId)
{
    BackgroundEntry =  _context.BackgroundEntry.Where(x => x.Id == sessionId).ToList();
}

但是这段代码需要300+毫秒的时间吗?

public async Task OnGetAsync(String sessionId)
{
    BackgroundEntry = await _context.BackgroundEntry.Where(x => x.Id == sessionId).ToListAsync();
}

我希望两者时间相同。

在不同的条件下进行测试,结果始终如此,异步有300多毫秒的延迟。

BackgroundEntry由EF自动生成:

public partial class BackgroundEntry
{
    public string Id { get; set; }
    public string Part { get; set; }
    public long Datetime { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid Session { get; set; }
    public string Action { get; set; }
    public string Domain { get; set; }
    public string Location { get; set; }
    public long? LastEntryDatetime { get; set; }

    public BackgroundEntry BackgroundEntryNavigation { get; set; }
    public BackgroundEntry InverseBackgroundEntryNavigation { get; set; }
}

使用秒表进行基准测试:

        using (Models.RecorderContext context = new Models.RecorderContext())
        {
            sw.Start();
            var BackgroundEntry = context.BackgroundEntry.Where(x => x.Id == sessionId).ToList();
            sw.Stop();
        }

        var g = sw.ElapsedMilliseconds;

        sw.Reset();
        // g = 22 ms

        using (Models.RecorderContext context = new Models.RecorderContext())
        {
            sw.Start();
            var BackgroundEntry = await context.BackgroundEntry.Where(x => x.Id == sessionId).ToListAsync();
            sw.Stop();
        }

        g = sw.ElapsedMilliseconds;

        // g = 328 ms

你能展示一下你是如何进行基准测试的吗?你是怎么发现这段代码需要超过300毫秒的时间?另外,你能分享一下BackgroundEntry的实现吗? - FCin
1
@FCin 性能报告由Chrome开发工具,网络统计数据提供。该页面在iisexpress和独立核心应用程序中以发布模式进行测试。本地运行,使用SQL Server版本12.0.4522.0的本地安装。EF Core版本为2.1... 对单个页面加载进行基准测试... - realPro
2
我建议进行更全面的基准测试。使用 StopWatch 包装调用,以确保您只测量数据库调用。我还建议使用新的 DbContext 进行隔离,以确保它没有带入其他缓存数据。在循环内进行多次调用也可能有所帮助,以便平均化“噪声”,并确保您没有处理 JIT 神器。 - Bradley Uffner
@BradleyUffner 我这里没有实现任何东西。这是默认的Visual Studio模板,名为“使用Entity Framework的Razor页面”。上面的代码是脚手架实现,所以我不会开始更改它... - realPro
我并不建议您永久更改它,但如果您想要我们的帮助来解决这个问题,您需要为我们提供一些诊断信息。 - Bradley Uffner
@BradleyUffner 添加了基准测试。 - realPro
2个回答

3
缺少上下文,无法做出精确的判断。一般而言,您可能错误地认为异步操作应该更快,而实际上恰恰相反。
异步操作是关于扩展规模,而非性能提升。它可以让您更有效地使用服务器资源,但这也会有一些成本,即使是最小的成本,也会对性能产生影响。处理异步操作存在开销、线程切换等问题,所有这些都可能使得同样的操作比同步操作更慢。但是,同步操作不提供利用空闲线程处理其他工作的机会,因此使用同步操作会限制服务器吞吐量的潜力。因此总体来说,将异步操作作为最佳方法是一个权衡考虑的结果。

1
你错误地认为异步应该更快。OP在哪里说了这个? - FCin
1
@FCin OP 没有必要明确地说出来。通过提供同步和异步两种实现方式,并询问为什么异步代码运行得更慢,我们可以合理地假设他们认为异步代码应该更快,或者至少是相同的速度。 - mason
6
很显然,+250ms的延迟并不是由异步操作引起的。如果调用异步操作会导致1/4秒的延迟,那么没有人会使用它。楼主写道:“我会期望两者的时间相同”,这实际上是应该发生的。对于.NET Core来说,异步操作的开销非常小。 - FCin

2

在 @rducom这里和@ChrisPratt给出的回答的基础上,如果您正在处理1MB <的数据,则此问题仍然存在于Microsoft.EntityFrameworkCore 6.0.0 中。

阻塞部分实际上是针对异步调用的SqlClient,而@AndriySvyryd推荐的解决方法是:

不要使用VARCHAR(MAX)或不要使用异步查询。

当我使用异步查询读取大型JSON对象和Image(二进制)数据时,就会发生这种情况。

链接:

https://github.com/dotnet/efcore/issues/18571#issuecomment-545992812

https://github.com/dotnet/efcore/issues/18571

https://github.com/dotnet/efcore/issues/885

https://github.com/dotnet/SqlClient/issues/245

https://github.com/dotnet/SqlClient/issues/593


希望这是被标记的答案,我花了半天时间得出了相同的结论。 - Akli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接