1

Which of these is a better approach for performance? Note: For some operations I have up to five independent queries to execute.

var foo = await context.foos.ToListAsync();
var bar = await context.bars.ToListAsync();

vs.

var fooTask = context1.foos.ToListAsync();
var barTask = context2.bars.ToListAsync();
await Task.WhenAll(fooTask , barTask);

It would be great to use the same context without awaits, but this answer mentions that is not possible.

Community
  • 1
  • 1
ermish
  • 1,160
  • 2
  • 13
  • 26
  • 2nd one looks better to me, create a List of tasks and then await them all – Ehsan Sajjad Mar 16 '16 at 12:58
  • If you care about performance don't use EF. Anyway, the second way will allow both queries to run concurrently, whether or not this performs better depends on other factors and is probably best ascertained by measurement on a representative environment under expected load. – Jodrell Mar 16 '16 at 13:06
  • @Jodrell what's the alternate then? – Ehsan Sajjad Mar 16 '16 at 13:08
  • @EʜsᴀɴSᴀᴊᴊᴀᴅ, there are several alternatives, I'd consider Dapper https://github.com/StackExchange/dapper-dot-net – Jodrell Mar 16 '16 at 13:11
  • 2
    I was more curious about the performance gain from parallel queries vs the cost of spinning up multiple contexts. – ermish Mar 16 '16 at 13:26

1 Answers1

2

As you found out, the DbContext is not thread safe, therefore the only option to really run the queries in parallel would be to create a new DbContext for each Thread/Task.

The overhead of creating a new DbContext is pretty low. https://msdn.microsoft.com/en-us/library/cc853327.aspx

Since the object will come from different DbContexts and to further increase performance I recommend you also use NoTracking()

Edit:

I made a simple test program with a database I had:

class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Warming up db context...");

        using (var db = new TestDbContext())
        {
            Console.WriteLine(db.AuditLogItems.ToList().Count);
        }

        // 1st run
        RunAsync();
        RunTasked();

        // 2nd run
        RunAsync();
        RunTasked();

        Console.ReadKey();
    }

    private static void RunAsync()
    {
        Task.Run(async () =>
        {
            var sw = Stopwatch.StartNew();
            List<AuditLogItem> list1;
            List<AuditLogItem> list2;

            using (var db = new TestDbContext())
            {
                list1 = await db.AuditLogItems.AsNoTracking().ToListAsync();
                list2 = await db.AuditLogItems.AsNoTracking().ToListAsync();
            }

            sw.Stop();
            Console.WriteLine("Executed {0} in {1}ms. | {2}", "Async", sw.ElapsedMilliseconds, list1.Count + " " + list2.Count);

        }).Wait();
    }

    private static void RunTasked()
    {
        Func<List<AuditLogItem>> runQuery = () =>
        {
            using (var db = new TestDbContext())
            {
                return db.AuditLogItems.AsNoTracking().ToList();
            }
        };

        var sw = Stopwatch.StartNew();
        var task1 = Task.Run(runQuery);
        var task2 = Task.Run(runQuery);

        Task.WaitAll(task1, task2);

        sw.Stop();
        Console.WriteLine("Executed {0} in {1}ms. | {2}", "Tasked", sw.ElapsedMilliseconds, task1.Result.Count + " " + task2.Result.Count);
    }
}

The output is:

Warming up db context...
5908
Executed Async in 293ms. | 5908 5908
Executed Tasked in 56ms. | 5908 5908
Executed Async in 194ms. | 5908 5908
Executed Tasked in 32ms. | 5908 5908

So yes, option 2 is faster...

Chris
  • 2,009
  • 1
  • 16
  • 25