Heavy await/async when the Visual Studio Debugger is attached is slow?

77 views Asked by At

I have a clean install of Visual Studio 2022 (community edition). I was trying to work out why my importer which utilises EF Core was so slow, but then noticed it's not actually the code itself that's slow, it's the VS debugger causing it to slow down.

I made a minimal code test to reproduce the issue (please don't critique/alter the code, this is purely a barebones example to highlight the issue). The real-world code is nothing like this, but it encounters the same issue.

    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");
            });
        }
    }

If I run this in debug mode with the debugger attached it's very slow, each 'search' thread takes 17-18s.

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

If I run it in debug mode without the debugger attached it's fast, each 'search' thread takes less than 1 second:

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

If I change the FirstOrDefaultAsync to the regular FirstOrDefault:

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

It's fast (< 1 second per search thread) both with and without the debugger attached.

Has anyone encountered this or have any idea what VS debugger setting might be causing it? Ideally I don't want to have to change to using the non-async FirstOrDefault purely to keep the debugger functioning correctly because when the debugger isn't attached it works fine.

I tried disabling the diagnostic tools (options -> debugging -> general -> enable diagnostic tools while debugging) but that made no difference.

0

There are 0 answers