附加 Visual Studio 调试器时大量等待/异步速度很慢?

问题描述 投票:0回答:1

我全新安装了 Visual Studio 2022(社区版)。我试图弄清楚为什么我的使用 EF Core 的导入器如此慢,但后来注意到实际上并不是代码本身慢,而是 VS 调试器导致它变慢。

我做了一个最小的代码测试来重现该问题(请不要批评/更改代码,这纯粹是一个突出问题的准系统示例)。现实世界的代码并非如此,但它遇到了同样的问题。

    public class TestItem
    {
        [Key]
        public Guid Id { get; set; }
        public int TestNum { get; set; }
    }

    public class AppDbContext : DbContext
    {
        public DbSet<TestItem> TestItems { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseNpgsql("Host=localhost;Database=efdebugtest;Username=postgres;Password=...");

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<TestItem>().HasIndex(r => r.TestNum);
        }
    }

    internal class Program
    {
        static async Task Main(string[] args)
        {
            void L(string str) => Console.WriteLine(str);

            using var ctx = new AppDbContext();
            await ctx.Database.EnsureDeletedAsync();
            await ctx.Database.MigrateAsync();
            var sw = new Stopwatch();
            sw.Start();
            var items = Enumerable.Range(0, 100_000).Select(i => new TestItem()
            {
                Id = Guid.NewGuid(),
                TestNum = i,
            });
            L($"Items Generated: {sw.Elapsed.TotalSeconds}s");

            sw.Restart();
            await ctx.AddRangeAsync(items);
            L($"Items Added: {sw.Elapsed.TotalSeconds}s");

            sw.Restart();
            await ctx.SaveChangesAsync();
            L($"Items Saved: {sw.Elapsed.TotalSeconds}s");

            ConcurrentBag<int> foundItems = new();
            var testChunks = Enumerable.Range(0, 200_000).OrderBy(_ => Guid.NewGuid()).Take(20_000).Chunk(2500);
            await Parallel.ForEachAsync(testChunks, async (chunk, cancellationToken) =>
            {
                using var threadCtx = new AppDbContext();
                var threadSw = new Stopwatch();
                threadSw.Start();
                foreach (var num in chunk)
                {
                    var dbEntry = await threadCtx.TestItems.FirstOrDefaultAsync(ti => ti.TestNum == num);
                    if (dbEntry != null)
                    {
                        foundItems.Add(dbEntry.TestNum);
                    }
                }
                L($"Thread finished search: {threadSw.Elapsed.TotalSeconds}s");
            });
        }
    }

如果我在附加调试器的调试模式下运行它,速度会非常慢,每个“搜索”线程需要 17-18 秒。

Items Generated: 0.0003297s
Items Added: 0.6718225s
Items Saved: 4.2752014s
Thread finished search: 17.2668618s
Thread finished search: 17.2820979s
...
Thread finished search: 17.4786316s
Thread finished search: 17.4891402s

如果我在调试模式下运行它没有附加调试器,它的速度很快,每个“搜索”线程花费的时间不到1秒:

Items Generated: 0.0002435s
Items Added: 0.6201017s
Items Saved: 4.025146s
Thread finished search: 0.8171215s
Thread finished search: 0.8336687s
...
Thread finished search: 0.8410876s
Thread finished search: 0.8419168s

如果我将

FirstOrDefaultAsync
更改为常规
FirstOrDefault
:

var dbEntry = threadCtx.TestItems.FirstOrDefault(ti => ti.TestNum == num);

速度很快(< 1 second per search thread) both with and without the debugger attached.

有没有人遇到过这个问题或者知道什么 VS 调试器设置可能会导致它?理想情况下,我不想纯粹为了保持调试器正常运行而更改为使用非异步 FirstOrDefault,因为当未连接调试器时它可以正常工作。

我尝试禁用诊断工具(选项 -> 调试 -> 常规 -> 调试时启用诊断工具),但这没有什么区别。

visual-studio async-await entity-framework-core visual-studio-2022 visual-studio-debugging
1个回答
0
投票

可能值得注意的是,如果您只是检查某个项目是否存在,则替换速度会快得多:

var dbEntry = await threadCtx.TestItems.FirstOrDefaultAsync(ti => ti.TestNum == num);
if (dbEntry != null)
{
    foundItems.Add(dbEntry.TestNum);
}

这样:

var itemExists = await threadCtx.TestItems.AnyAsync(ti => ti.TestNum == num);
if (itemExists)
{
    foundItems.Add(num);
}

原始代码每次都会获取并跟踪整个实体,而如果找到匹配项,您所保留的只是“TestNum”值。

如果您确实需要从检索到的 TestItem 中获取其他值,则使用

Select()
从找到的行中获取所需的 ID/值会更有效。如果您确实想做一些事情,例如将实体本身存储在
List
中,则使用:

var testItem = await threadCtx.TestItems
    .AsNoTracking()
    .FirstOrDefaultAsync(ti => ti.TestNum == num);

...确保它已分离并且 DbContext 不会跟踪它。

© www.soinside.com 2019 - 2024. All rights reserved.