添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
开朗的毛衣  ·  numpy.unique() in ...·  1 年前    · 
含蓄的山楂  ·  pandas-07 ...·  1 年前    · 
痴情的啄木鸟  ·  java ...·  1 年前    · 

Entity Framework的异步操作需要十倍的时间来完成

166 人关注

我有一个MVC网站,使用Entity Framework 6来处理数据库,我一直在尝试改变它,使所有的东西都以同步控制器的方式运行,对数据库的调用也以其同步对应的方式运行(例如,ToListAsync()而不是ToList()

我遇到的问题是,简单地将我的查询改为异步,就会导致它们变得异常缓慢。

下面的代码从我的数据上下文中获得一个 "相册 "对象的集合,并被转换为一个相当简单的数据库连接。

// Get the albums
var albums = await this.context.Albums
    .Where(x => x.Artist.ID == artist.ID)
    .ToListAsync();

下面是创建的SQL。

exec sp_executesql N'SELECT 
[Extent1].[ID] AS [ID], 
[Extent1].[URL] AS [URL], 
[Extent1].[ASIN] AS [ASIN], 
[Extent1].[Title] AS [Title], 
[Extent1].[ReleaseDate] AS [ReleaseDate], 
[Extent1].[AccurateDay] AS [AccurateDay], 
[Extent1].[AccurateMonth] AS [AccurateMonth], 
[Extent1].[Type] AS [Type], 
[Extent1].[Tracks] AS [Tracks], 
[Extent1].[MainCredits] AS [MainCredits], 
[Extent1].[SupportingCredits] AS [SupportingCredits], 
[Extent1].[Description] AS [Description], 
[Extent1].[Image] AS [Image], 
[Extent1].[HasImage] AS [HasImage], 
[Extent1].[Created] AS [Created], 
[Extent1].[Artist_ID] AS [Artist_ID]
FROM [dbo].[Albums] AS [Extent1]
WHERE [Extent1].[Artist_ID] = @p__linq__0',N'@p__linq__0 int',@p__linq__0=134

就目前的情况来看,这并不是一个非常复杂的查询,但是SQL服务器运行它几乎需要6秒钟。SQL Server Profiler报告说,它需要5742ms来完成。

如果我把我的代码改为。

// Get the albums
var albums = this.context.Albums
    .Where(x => x.Artist.ID == artist.ID)
    .ToList();

那么就会生成完全相同的SQL,但是根据SQL Server Profiler的报告,这个运行时间只有474ms。

数据库中的 "专辑 "表大约有3500行,其实并不多,而且在 "艺术家_ID "列上有一个索引,所以应该是相当快的。

我知道Async有开销,但让事情变得慢十倍,对我来说似乎有点太陡了!我在哪里出了问题?我在哪里做错了?

8 个评论
在我看来,这是不对的。如果你用同样的数据执行同样的查询,SQL Server Profiler报告的执行时间应该是差不多的,因为async是在c#中发生的,不是Sql。Sql server甚至不知道你的c#代码是异步的。
当你第一次运行生成的查询时,编译查询(建立执行计划......)可能需要更长的时间,从第二次开始,同样的查询可能会更快(Sql server缓存了查询),但应该不会有太大的不同。
EF用async应该是相当快的 。你对这个查询进行了多少次测量?你确定差异是因为async/await而不是因为一些外部因素吗?正如@KhanhTO所提到的,我觉得分析器中的时间应该是完全一样的。
我已经运行了多次,使用异步时总是在6秒左右,同步运行时在0.5秒左右。这真的很奇怪--就像你说的,当时是在SQL Server中运行的,所以不应该有什么区别,但它确实如此,我也不知道为什么。
usr
你需要确定什么是慢的。在一个无限循环中运行查询。暂停调试器10次。它最常停在哪里?发布包括外部代码在内的堆栈。
你确定你的两个版本的查询都包括 "exec sp_executesql "吗?对我来说,这肯定是导致速度下降的一个原因。EF不应该像这样通过评估器执行SQL。
看起来问题出在Image属性上,我完全忘记了这个问题。这是一个VARBINARY(MAX)列,所以肯定会导致速度变慢,但还是有点奇怪,速度变慢只在运行异步时才成为问题。我已经重组了我的数据库,使图片成为一个链接表的一部分,现在一切都快多了。
usr
问题可能是,EF正在向ADO.NET发出大量的异步读取,以检索所有这些字节和行。这样一来,开销就被放大了。由于你没有进行我所要求的测量,我们永远不会知道。问题似乎已经解决了。
c#
entity-framework
asynchronous
async-await
Dylan Parry
Dylan Parry
发布于 2015-02-16
5 个回答
rducom
rducom
发布于 2022-03-22
已采纳
0 人赞同

我发现这个问题非常有趣,尤其是我正在用Ado.Net和EF 6到处使用 async 。我希望有人能给这个问题一个解释,但它并没有发生。所以我试着在我这边重现了这个问题。我希望你们中的一些人会觉得这很有趣。

第一个好消息是:我重现了这个问题:)差异是巨大的。以8的系数......

首先,我怀疑有什么东西在处理 CommandBehavior 因为 我读了一篇 关于 async 与Ado的 有趣文章 ,说的是这个。

"由于非顺序访问模式必须存储整个行的数据,如果你从服务器上读取一个大列(如varbinary(MAX)、varchar(MAX)、nvarchar(MAX)或XML),它可能会导致问题。"

CommandBehavior.SequentialAccess 我怀疑 ToList() ,而异步调用则是 CommandBehavior.Default (非连续的,这可能会导致问题)。所以我下载了EF6的源代码,并在所有地方设置了断点(当然是在使用 CommandBehavior 的地方)。

结果: 什么都没有 。所有的调用都是用 CommandBehavior.Default ....。所以我试着进入EF代码以了解发生了什么......然后......呜呜呜......我从来没有见过这样的委托代码。我从来没有见过这样的委托代码,所有的东西似乎都是懒得执行的......

所以我试图做一些剖析来了解发生了什么......

我想我找到了一些东西...

这是创建我的基准表的模型,里面有3500行,每个 varbinary(MAX) ,有256Kb的随机数据。(EF 6.1 - CodeFirst - CodePlex )。

public class TestContext : DbContext
    public TestContext()
        : base(@"Server=(localdb)\\v11.0;Integrated Security=true;Initial Catalog=BENCH") // Local instance
    public DbSet<TestItem> Items { get; set; }
public class TestItem
    public int ID { get; set; }
    public string Name { get; set; }
    public byte[] BinaryData { get; set; }

这是我用来创建测试数据的代码,并对EF进行了基准测试。

using (TestContext db = new TestContext())
    if (!db.Items.Any())
        foreach (int i in Enumerable.Range(0, 3500)) // Fill 3500 lines
            byte[] dummyData = new byte[1 << 18];  // with 256 Kbyte
            new Random().NextBytes(dummyData);
            db.Items.Add(new TestItem() { Name = i.ToString(), BinaryData = dummyData });
        await db.SaveChangesAsync();
using (TestContext db = new TestContext())  // EF Warm Up
    var warmItUp = db.Items.FirstOrDefault();
    warmItUp = await db.Items.FirstOrDefaultAsync();
Stopwatch watch = new Stopwatch();
using (TestContext db = new TestContext())
    watch.Start();
    var testRegular = db.Items.ToList();
    watch.Stop();
    Console.WriteLine("non async : " + watch.ElapsedMilliseconds);
using (TestContext db = new TestContext())
    watch.Restart();
    var testAsync = await db.Items.ToListAsync();
    watch.Stop();
    Console.WriteLine("async : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
    await connection.OpenAsync();
    using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
        watch.Restart();
        List<TestItem> itemsWithAdo = new List<TestItem>();
        var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
        while (await reader.ReadAsync())
            var item = new TestItem();
            item.ID = (int)reader[0];
            item.Name = (String)reader[1];
            item.BinaryData = (byte[])reader[2];
            itemsWithAdo.Add(item);
        watch.Stop();
        Console.WriteLine("ExecuteReaderAsync SequentialAccess : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
    await connection.OpenAsync();
    using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
        watch.Restart();
        List<TestItem> itemsWithAdo = new List<TestItem>();
        var reader = await cmd.ExecuteReaderAsync(CommandBehavior.Default);
        while (await reader.ReadAsync())
            var item = new TestItem();
            item.ID = (int)reader[0];
            item.Name = (String)reader[1];
            item.BinaryData = (byte[])reader[2];
            itemsWithAdo.Add(item);
        watch.Stop();
        Console.WriteLine("ExecuteReaderAsync Default : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
    await connection.OpenAsync();
    using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
        watch.Restart();
        List<TestItem> itemsWithAdo = new List<TestItem>();
        var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
        while (reader.Read())
            var item = new TestItem();
            item.ID = (int)reader[0];
            item.Name = (String)reader[1];
            item.BinaryData = (byte[])reader[2];
            itemsWithAdo.Add(item);
        watch.Stop();
        Console.WriteLine("ExecuteReader SequentialAccess : " + watch.ElapsedMilliseconds);
using (var connection = new SqlConnection(CS))
    await connection.OpenAsync();
    using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
        watch.Restart();
        List<TestItem> itemsWithAdo = new List<TestItem>();
        var reader = cmd.ExecuteReader(CommandBehavior.Default);
        while (reader.Read())
            var item = new TestItem();
            item.ID = (int)reader[0];
            item.Name = (String)reader[1];
            item.BinaryData = (byte[])reader[2];
            itemsWithAdo.Add(item);
        watch.Stop();
        Console.WriteLine("ExecuteReader Default : " + watch.ElapsedMilliseconds);

对于常规的EF调用(.ToList()),分析似乎是 "正常的",很容易阅读。

在这里,我们找到了8.4秒的秒表(剖析使perfs变慢)。我们还发现沿调用路径的HitCount = 3500,这与测试中的3500行一致。在TDS解析器方面,情况开始变得更糟,因为我们在TryReadByteArray() 方法上读到118 353次调用,这是缓冲循环发生的地方。(每个256kb的byte[] ,平均有33.8个调用)。

对于async ,情况真的非常不同....。首先,.ToListAsync() 的调用被安排在ThreadPool上,然后被等待。这里没有什么惊人之处。但是,现在,在ThreadPool上的async 地狱。

首先,在第一种情况下,我们在整个调用路径上只有3500个点击次数,而在这里,我们有118 371个。此外,你必须想象一下我没有放在屏幕上的所有同步调用......

第二,在第一种情况下,我们对TryReadByteArray() 方法的调用 "只有118 353次",而在这里我们有2050 210次调用!这是17倍之多。这是17倍之多...(在一个1Mb大数组的测试中,它是160倍)

此外,还有:

  • 创建了120 000个Task 实例
  • 727 519Interlocked 的调用
  • 290 569Monitor 调用
  • 98 283个ExecutionContext 实例,有264 481个捕获量
  • 208 733 次SpinLock 调用
  • 我的猜测是,缓冲是以异步方式进行的(而且不是一个好的方式),并行的任务试图从TDS中读取数据。为了解析二进制数据,创建了太多的任务。

    作为一个初步的结论,我们可以说Async是伟大的,EF6是伟大的,但是EF6的Async的使用在它目前的实现中增加了一个主要的开销,在性能方面,在线程方面,在CPU方面(在ToList() 的情况下CPU使用率为12%,在ToListAsync 的情况下为20%,工作时间为8到10倍...我在一个旧的i7 920上运行它)。

    在做一些测试的时候,我想到了这篇文章,我注意到我错过了一些东西。

    "对于.Net 4.5中的新异步方法,其行为与同步方法完全相同,只有一个明显的例外。非顺序模式下的ReadAsync"。

    什么?!!?

    因此,我扩展了我的基准,包括Ado.Net的常规/异步调用,以及CommandBehavior.SequentialAccess /CommandBehavior.Default ,这里有一个很大的惊喜!:

    我们在使用Ado.Net时有完全相同的行为!!。打脸...。

    我的明确结论是:EF 6的实现有一个错误。当对一个包含binary(max) 列的表进行异步调用时,它应该将CommandBehavior 切换到SequentialAccess 。创建太多的任务,减慢进程的问题是在Ado.Net方面。EF的问题是,它没有按规定使用Ado.Net。

    现在你知道了,与其使用EF6的异步方法,不如用常规的非异步方式调用EF,然后用TaskCompletionSource<T> ,以异步方式返回结果。

    注1:我编辑了我的帖子,因为有一个可耻的错误....。我在网络上做了第一次测试,而不是在本地,有限的带宽使结果失真。以下是最新的结果。

    注2:我没有把我的测试扩展到其他使用情况(例如:nvarchar(max) ,有大量的数据),但有可能发生同样的行为。

    注3:在ToList() 的情况下,通常的情况是12%的CPU(1/8的CPU=1个逻辑核心)。不寻常的是,在ToListAsync() 的情况下,最大的20%,好像调度器不能使用所有的Treads。这可能是由于创建了太多的任务,或者是TDS解析器的瓶颈,我不知道......

    我在codeplex上开了一个问题,希望他们能做点什么。 entityframework.codeplex.com/workitem/2686
    我在github上托管的新EF代码Repo上开了一个问题: github.com/aspnet/EntityFramework6/issues/88
    遗憾的是,GitHub上的问题已经被关闭,建议不要在varbinary中使用async。理论上,varbinary应该是async最合理的情况,因为在文件传输的过程中,线程会被阻断更长时间。那么,如果我们想在数据库中保存二进制数据,我们现在该怎么做?
    有谁知道这在EF Core中是否仍然是个问题?我一直无法找到任何信息或基准。
    @AndrewLewis 我没有什么科学依据,但是我在使用EF Core的时候遇到了连接池反复超时的问题,导致问题的两个查询是 .ToListAsync() .CountAsync() ... 对于其他发现这个评论线程的人, 这个查询 可能会有帮助。 祝你顺利。
    Xeno-D
    Xeno-D
    发布于 2022-03-22
    0 人赞同

    因为我几天前得到了这个问题的链接,我决定发布一个小的更新。我能够使用目前最新版本的EF(6.4.0)和.NET框架4.7.2重现 原始答案 中的结果。令人惊讶的是,这个问题从未得到改善。

    .NET Framework 4.7.2 | EF 6.4.0 (Values in ms. Average of 10 runs)
    non async : 3016
    async : 20415
    ExecuteReaderAsync SequentialAccess : 2780
    ExecuteReaderAsync Default : 21061
    ExecuteReader SequentialAccess : 3467
    ExecuteReader Default : 3074
    

    这就引出了一个问题。dotnet core中是否有改进?

    我把原答案中的代码复制到一个新的dotnet core 3.1.3项目中,并加入了EF Core 3.1.3。结果是。

    dotnet core 3.1.3 | EF Core 3.1.3 (Values in ms. Average of 10 runs)
    non async : 2780
    async : 6563
    ExecuteReaderAsync SequentialAccess : 2593
    ExecuteReaderAsync Default : 6679
    ExecuteReader SequentialAccess : 2668
    ExecuteReader Default : 2315
    

    令人惊讶的是有了很大的改进。由于线程库被调用,似乎仍有一些时间滞后,但它比.NET框架的实现快了3倍左右。

    我希望这个答案能帮助其他在未来得到这种发送方式的人。

    panoskj
    panoskj
    发布于 2022-03-22
    0 人赞同

    有一个解决方案,可以在不牺牲性能的情况下使用异步,在EF Core和MS SQL数据库中进行了测试。

    首先,你需要为 DBDataReader 做一个包装器。

  • 它的 ReadAsync 方法应该读取整个行,将每一列的值存储在一个缓冲区中。
  • 它的 GetXyz 方法应该从上述的缓冲区获取数值。
  • 可以选择使用 GetBytes + Encoding.GetString ,而不是 GetString 。对于我的使用情况(每行16KB的文本列),这使得同步和异步的速度明显加快。
  • 可以选择调整你的连接字符串的数据包大小。对于我的用例,32767的值使同步和异步的速度都有明显的提高。
  • 现在你可以做一个 DbCommandInterceptor ,拦截 ReaderExecutingAsync ,创建一个有顺序访问的 DBDataReader ,由上述的包装器包装。

    EF Core会尝试以非顺序的方式访问字段--这就是为什么包装器必须先读取和缓冲整个行。

    下面是一个实现的例子(同时拦截异步和同步)。

    /// <summary>
    /// This interceptor optimizes a <see cref="Microsoft.EntityFrameworkCore.DbContext"/> for
    /// accessing large columns (text, ntext, varchar(max) and nvarchar(max)). It enables the
    /// <see cref="CommandBehavior.SequentialAccess"/> option and uses an optimized method
    /// for converting large text columns into <see cref="string"/> objects.
    /// </summary>
    public class ExampleDbCommandInterceptor : DbCommandInterceptor
        public async override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
            var behavior = CommandBehavior.SequentialAccess;
            var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false);
            var wrapper = await DbDataReaderOptimizedWrapper.CreateAsync(reader, cancellationToken).ConfigureAwait(false);
            return InterceptionResult<DbDataReader>.SuppressWithResult(wrapper);
        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
            var behavior = CommandBehavior.SequentialAccess;
            var reader = command.ExecuteReader(behavior);
            var wrapper = DbDataReaderOptimizedWrapper.Create(reader);
            return InterceptionResult<DbDataReader>.SuppressWithResult(wrapper);
        /// <summary>
        /// This wrapper caches the values of accessed columns of each row, allowing non-sequential access
        /// even when <see cref="CommandBehavior.SequentialAccess"/> is specified. It enables using this option it with EF Core.
        /// In addition, it provides an optimized method for reading text, ntext, varchar(max) and nvarchar(max) columns.
        /// All in all, it speeds up database operations reading from large text columns.
        /// </summary>
        sealed class DbDataReaderOptimizedWrapper : DbDataReader
            readonly DbDataReader reader;
            readonly DbColumn[] schema;
            readonly object[] cache;
            readonly Func<object>[] materializers;
            [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
            private T Get<T>(int ordinal)
                if (cache[ordinal] != DBNull.Value) return (T)cache[ordinal];
                return (T)(object)null; // this line will throw an exception if T is not a reference type (class), otherwise it will return null
            private DbDataReaderOptimizedWrapper(DbDataReader reader, IEnumerable<DbColumn> schema)
                this.reader = reader;
                this.schema = schema.OrderBy(x => x.ColumnOrdinal).ToArray();
                cache = new object[this.schema.Length];
                byte[] stringGetterBuffer = null;
                string stringGetter(int i)
                    var dbColumn = this.schema[i];
                    // Using GetBytes instead of GetString is much faster, but only works for text, ntext, varchar(max) and nvarchar(max)
                    if (dbColumn.ColumnSize < int.MaxValue) return reader.GetString(i);
                    if (stringGetterBuffer == null) stringGetterBuffer = new byte[32 * 1024];
                    var totalRead = 0;
                    while (true)
                        var offset = totalRead;
                        totalRead += (int)reader.GetBytes(i, offset, stringGetterBuffer, offset, stringGetterBuffer.Length - offset);
                        if (totalRead < stringGetterBuffer.Length) break;
                        const int maxBufferSize = int.MaxValue / 2;
                        if (stringGetterBuffer.Length >= maxBufferSize)
                            throw new OutOfMemoryException($"{nameof(DbDataReaderOptimizedWrapper)}.{nameof(GetString)} cannot load column '{GetName(i)}' because it contains a string longer than {maxBufferSize} bytes.");
                        Array.Resize(ref stringGetterBuffer, 2 * stringGetterBuffer.Length);
                    var c = dbColumn.DataTypeName[0];
                    var encoding = (c is 'N' or 'n') ? Encoding.Unicode : Encoding.ASCII;
                    return encoding.GetString(stringGetterBuffer.AsSpan(0, totalRead));
                var dict = new Dictionary<Type, Func<DbColumn, int, Func<object>>>
                    [typeof(bool)] = (column, index) => () => reader.GetBoolean(index),
                    [typeof(byte)] = (column, index) => () => reader.GetByte(index),
                    [typeof(char)] = (column, index) => () => reader.GetChar(index),
                    [typeof(short)] = (column, index) => () => reader.GetInt16(index),
                    [typeof(int)] = (column, index) => () => reader.GetInt32(index),
                    [typeof(long)] = (column, index) => () => reader.GetInt64(index),
                    [typeof(float)] = (column, index) => () => reader.GetFloat(index),
                    [typeof(double)] = (column, index) => () => reader.GetDouble(index),
                    [typeof(decimal)] = (column, index) => () => reader.GetDecimal(index),
                    [typeof(DateTime)] = (column, index) => () => reader.GetDateTime(index),
                    [typeof(Guid)] = (column, index) => () => reader.GetGuid(index),
                    [typeof(string)] = (column, index) => () => stringGetter(index),
                materializers = schema.Select((column, index) => dict[column.DataType](column, index)).ToArray();
            public static DbDataReaderOptimizedWrapper Create(DbDataReader reader) 
                => new DbDataReaderOptimizedWrapper(reader, reader.GetColumnSchema());
            public static async ValueTask<DbDataReaderOptimizedWrapper> CreateAsync(DbDataReader reader, CancellationToken cancellationToken) 
                => new DbDataReaderOptimizedWrapper(reader, await reader.GetColumnSchemaAsync(cancellationToken).ConfigureAwait(false));
            protected override void Dispose(bool disposing) => reader.Dispose();
            public async override ValueTask DisposeAsync() => await reader.DisposeAsync().ConfigureAwait(false);
            public override object this[int ordinal] => Get<object>(ordinal);
            public override object this[string name] => Get<object>(GetOrdinal(name));
            public override int Depth => reader.Depth;
            public override int FieldCount => reader.FieldCount;
            public override bool HasRows => reader.HasRows;
            public override bool IsClosed => reader.IsClosed;
            public override int RecordsAffected => reader.RecordsAffected;
            public override int VisibleFieldCount => reader.VisibleFieldCount;
            public override bool GetBoolean(int ordinal) => Get<bool>(ordinal);
            public override byte GetByte(int ordinal) => Get<byte>(ordinal);
            public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => throw new NotSupportedException();
            public override char GetChar(int ordinal) => Get<char>(ordinal);
            public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => throw new NotSupportedException();
            public override string GetDataTypeName(int ordinal) => reader.GetDataTypeName(ordinal);
            public override DateTime GetDateTime(int ordinal) => Get<DateTime>(ordinal);
            public override decimal GetDecimal(int ordinal) => Get<decimal>(ordinal);
            public override double GetDouble(int ordinal) => Get<double>(ordinal);
            public override IEnumerator GetEnumerator() => reader.GetEnumerator();
            public override Type GetFieldType(int ordinal) => reader.GetFieldType(ordinal);
            public override float GetFloat(int ordinal) => Get<float>(ordinal);
            public override Guid GetGuid(int ordinal) => Get<Guid>(ordinal);
            public override short GetInt16(int ordinal) => Get<short>(ordinal);
            public override int GetInt32(int ordinal) => Get<int>(ordinal);
            public override long GetInt64(int ordinal) => Get<long>(ordinal);
            public override string GetName(int ordinal) => reader.GetName(ordinal);
            public override int GetOrdinal(string name) => reader.GetOrdinal(name);
            public override string GetString(int ordinal) => Get<string>(ordinal);
            public override object GetValue(int ordinal) => Get<object>(ordinal);
            public override int GetValues(object[] values)
                var min = Math.Min(cache.Length, values.Length);
                Array.Copy(cache, values, min);
                return min;
            public override bool IsDBNull(int ordinal) => Convert.IsDBNull(cache[ordinal]);
            public override bool NextResult() => reader.NextResult();
            public override bool Read()
                Array.Clear(cache, 0, cache.Length);
                if (reader.Read())
                    for (int i = 0; i < cache.Length; ++i)
                        if ((schema[i].AllowDBNull ?? true) && reader.IsDBNull(i)) 
                            cache[i] = DBNull.Value;
                        else cache[i] = materializers[i]();
                    return true;
                return false;
            public override void Close() => reader.Close();
            public async override Task CloseAsync() => await reader.CloseAsync().ConfigureAwait(false);
            public override DataTable GetSchemaTable() => reader.GetSchemaTable();
            public async override Task<DataTable> GetSchemaTableAsync(CancellationToken cancellationToken = default) => await reader.GetSchemaTableAsync(cancellationToken).ConfigureAwait(false);
            public async override Task<ReadOnlyCollection<DbColumn>> GetColumnSchemaAsync(CancellationToken cancellationToken = default) => await reader.GetColumnSchemaAsync(cancellationToken).ConfigureAwait(false);
            public async override Task<bool> NextResultAsync(CancellationToken cancellationToken) => await reader.NextResultAsync(cancellationToken).ConfigureAwait(false);
            public async override Task<bool> ReadAsync(CancellationToken cancellationToken)
                Array.Clear(cache, 0, cache.Length);
                if (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
                    for (int i = 0; i < cache.Length; ++i)
                        if ((schema[i].AllowDBNull ?? true) && await reader.IsDBNullAsync(i, cancellationToken).ConfigureAwait(false)) 
                            cache[i] = DBNull.Value;
                        else cache[i] = materializers[i]();
                    return true;
                return false;
    

    我现在无法提供一个基准,希望有人能在评论中这样做。

    Ogglas
    Ogglas
    发布于 2022-03-22
    0 人赞同

    补充一下@rducom给出的答案。这个问题仍然存在于 Microsoft.EntityFrameworkCore 6.0.0

    阻塞部分其实是 SqlClient ,@AndriySvyryd推荐的在EF核心项目上有效的解决方法是。

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

    我在用 async 查询读取一个大的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