【问题标题】:Entity Framework async operation takes ten times as long to complete实体框架异步操作需要十倍的时间才能完成
【发布时间】:2015-04-17 01:46:42
【问题描述】:

我有一个 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 Server 运行它需要将近 6 秒。 SQL Server Profiler 报告它需要 5742 毫秒才能完成。

如果我将代码更改为:

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

然后生成完全相同的 SQL,但根据 SQL Server Profiler,它只运行了 474 毫秒。

数据库在“Albums”表中有大约 3500 行,这并不是很多,并且在“Artist_ID”列上有一个索引,所以应该很快。

我知道异步有开销,但是让事情慢十倍对我来说似乎有点陡峭!我哪里错了?

【问题讨论】:

  • 我觉得不合适。如果使用相同的数据执行相同的查询,SQL Server Profiler 报告的执行时间应该或多或少相同,因为异步是在 c# 中发生的,而不是 Sql。 Sql server 甚至不知道你的 c# 代码是异步的
  • 第一次运行生成的查询时,编译查询可能需要更长的时间(构建执行计划,...),从第二次开始,相同的查询可能会更快( Sql server缓存查询),但应该不会有太大差异。
  • 你需要确定什么是慢的。在无限循环中运行查询。暂停调试器 10 次。它最常停在哪里?发布包含外部代码的堆栈。
  • 看起来问题与 Image 属性有关,我完全忘记了。它是一个 VARBINARY(MAX) 列,因此必然会导致缓慢,但缓慢仅成为异步运行的问题仍然有点奇怪。我已经重组了我的数据库,使图像现在成为链接表的一部分,并且现在一切都快得多了。
  • 问题可能是 EF 向 ADO.NET 发出大量异步读取以检索所有这些字节和行。这样开销就被放大了。由于您没有执行我要求的测量,我们永远不会知道。问题似乎解决了。

标签: c# entity-framework asynchronous async-await


【解决方案1】:

我发现这个问题非常有趣,特别是因为我在 Ado.Net 和 EF 6 的任何地方都使用async。我希望有人对这个问题给出解释,但它没有发生。所以我试图在我这边重现这个问题。我希望你们中的一些人会觉得这很有趣。

第一个好消息:我复制了它:) 差异是巨大的。系数为 8 ...

首先,我怀疑与 CommandBehavior 有什么关系,因为 I read an interesting article 与 Ado 关于 async 的关系是这样的:

"由于非顺序访问模式必须存储整行的数据,如果您从服务器读取大列(例如 varbinary(MAX)、varchar(MAX)、nvarchar(MAX ) 或 XML)。”

我怀疑 ToList() 调用是 CommandBehavior.SequentialAccess 和异步调用是 CommandBehavior.Default (非顺序的,这可能会导致问题)。因此,我下载了 EF6 的源代码,并在各处放置断点(当然,在使用 CommandBehavior 的地方)。

结果:没有。所有的调用都是用CommandBehavior.Default 进行的......所以我试图进入 EF 代码以了解会发生什么......而且......哦哦......我从来没有见过这样的委托代码,一切似乎都懒惰执行......

所以我尝试进行一些分析以了解发生了什么......

而且我觉得我有一些东西......

这是创建我进行基准测试的表的模型,其中包含 3500 行,每个 varbinary(MAX) 中有 256 Kb 随机数据。 (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 秒(分析会减慢性能)。我们还发现沿调用路径的 HitCount = 3500,这与测试中的 3500 行一致。在 TDS 解析器方面,事情开始变得更糟,因为我们在 TryReadByteArray() 方法上读取了 118 353 次调用,这就是缓冲循环发生的原因。 (256kb 的每个 byte[] 平均调用 33.8 次)

对于async 的情况,真的很不一样......首先,.ToListAsync() 调用被安排在 ThreadPool 上,然后等待。这里没有什么了不起的。但是,现在,这是 ThreadPool 上的 async 地狱:

首先,在第一种情况下,我们在整个调用路径中只有 3500 个命中计数,这里我们有 118 371 个。此外,您必须想象我没有放在屏幕截图上的所有同步调用......

其次,在第一种情况下,我们对 TryReadByteArray() 方法的调用“只有 118 353 次”,这里我们有 2 050 210 次调用!它是 17 倍……(在一个 1Mb 大阵列的测试中,它是 160 倍)

还有:

  • 已创建 120 000 个Task 实例
  • 727 519 Interlocked 来电
  • 290 569 Monitor 致电
  • 98 283 ExecutionContext 实例,有 264 481 次捕获
  • 208 733 SpinLock 电话

我的猜测是缓冲是以异步方式进行的(而且不是一种好的方式),并行任务试图从 TDS 读取数据。创建太多任务只是为了解析二进制数据。

作为初步结论,我们可以说 Async 很棒,EF6 很棒,但是 EF6 在其当前实现中使用异步增加了主要开销,在性能方面、线程方面和 CPU 方面(12% CPU ToList() 案例中的使用率和 ToListAsync 案例中的 20% 的使用时间延长了 8 到 10 倍......我在旧的 i7 920 上运行它)。

在做一些测试时,我在想this article again,我注意到我想念的东西:

“对于 .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&lt;T&gt; 以异步方式返回结果。

注意 1 :由于一个可耻的错误,我编辑了我的帖子....这是更新的结果。

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

注 3:ToList() 的情况通常是 12% CPU(我的 CPU 的 1/8 = 1 个逻辑核心)。不寻常的是ToListAsync() 案例的最大 20%,好像调度程序无法使用所有 Treads。可能是创建的Task太多,也可能是TDS解析器的瓶颈,我不知道...

【讨论】:

  • 我在 codeplex 上打开了一个问题,希望他们能做点什么。 entityframework.codeplex.com/workitem/2686
  • 我在 github 上托管的新 EF 代码仓库上打开了一个问题:github.com/aspnet/EntityFramework6/issues/88
  • 遗憾的是,GitHub 上的问题已关闭,建议不要将异步与 varbinary 一起使用。理论上 varbinary 应该是异步最有意义的情况,因为在传输文件时线程将被阻塞更长时间。那么如果我们想在数据库中保存二进制数据,我们现在该怎么做呢?
  • 有人知道这是否仍然是 EF Core 中的问题吗?我一直找不到任何信息或基准。
  • @AndrewLewis 我没有任何科学依据,但我与 EF Core 重复连接池超时,其中导致问题的两个查询是 .ToListAsync().CountAsync()...这个评论线程,this query 可能会有所帮助。神速。
【解决方案2】:

因为几天前我得到了这个问题的链接,所以我决定发布一个小更新。我能够使用当前最新版本的 EF (6.4.0) 和 .NET Framework 4.7.2 重现 original answer 的结果。令人惊讶的是,这个问题从未得到改善。

.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 Framework 实现快了大约 3 倍。

我希望这个答案可以帮助其他人将来以这种方式发送。

【讨论】:

    【解决方案3】:

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

    首先,您需要为DBDataReader 制作一个包装器:

    1. 它的ReadAsync 方法应该读取整行,将每列的值存储在缓冲区中。
    2. 它的GetXyz 方法应该从上述缓冲区中获取值。
    3. (可选)使用GetBytes + Encoding.GetString 而不是GetString。对于我的用例(每行 16KB 文本列),它显着提高了同步和异步的速度。
    4. (可选)调整连接字符串的数据包大小。对于我的用例,值 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;
            }
        }
    }
    

    我现在无法提供基准,希望在 cmets 中有人会这样做。

    【讨论】:

      【解决方案4】:

      添加到@rducom 给出的答案。 Microsoft.EntityFrameworkCore 6.0.0中仍然存在此问题

      阻塞部分实际上是SqlClient,@AndriySvyryd 推荐的适用于 EF 核心项目的解决方法是:

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

      这发生在我使用async 查询读取大型 JSON 对象和图像(二进制)数据时。

      链接:

      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

      【讨论】:

        猜你喜欢
        • 2012-02-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-23
        • 1970-01-01
        • 2018-02-07
        相关资源
        最近更新 更多