0

I have the following code:

public class SomeClass{
    public async Task DoSomething(){
        Task<Result1> result1Task = GetResult1Async();
        Task<Result2> result2Task = GetResult2Async();
        await Task.WhenAll(result1Task , result2Task);
        // continues code ..
    }

    private Task<Result1> GetResult1Async(){
        return _context.Result1.ToListAsync();
    }

    private Task<Result2> GetResult2Async(){
        return _context.Result2.ToListAsync();
    }
}

Debugging or running this code the debugger waits until each task is finished seperately instead of remembering the task and only wait at row Task.WhenAll. Is there anything wrong respectively how do I get that running async?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
SNO
  • 793
  • 1
  • 10
  • 30
  • 6
    FYI if `_context` is an Entity Framework DbContext, then this code is dangerous and you cannot run multiple queries in a single context at the same time. – DavidG Jul 24 '23 at 10:07
  • @DavidG Thank you for your answear. Yes, this is a EntityFramework-DbSet. Why EF offers than ```ToListAsync``` when this is not possible? – SNO Jul 24 '23 at 10:11
  • 3
    You need to use a different `DbContext` instance for each `ToListAsync`. The `DbContext` is not thread-safe. It supports only one operation at a time. It doesn't support concurrency. – Theodor Zoulias Jul 24 '23 at 10:19
  • 1
    ^^ _and_ "concurrent" is not neccessarily equal to "asynchronous". – Fildor Jul 24 '23 at 10:24
  • What database are you hitting? Is it Oracle by any chance? – Guru Stron Jul 24 '23 at 11:04
  • 1
    @TheodorZoulias Whether or not parallelizing database operations in this way is a good idea is anyway debatable. Databases generally already have parallization built in, and are normally heavily IO bound, so parallelizing further usually increases overall time rather than decreasing/speeding up. – Charlieface Jul 24 '23 at 11:45
  • @Charlieface yes, this is true. One case where parallelizing DB operations might be beneficial, is when the database is located on a different machine in the local network, and the network latency is not negligible. – Theodor Zoulias Jul 24 '23 at 12:23
  • 1
    @TheodorZoulias Or possibly the same server, but tables are stored on different disks, and network bandwidth is wide enough. But in the majority of cases this is not true. – Charlieface Jul 24 '23 at 12:36

2 Answers2

1

Asynchronous methods in general create an incomplete Task very fast, much faster than the time it takes for this Task to complete. That's how asynchronous methods are supposed to behave according to Microsoft's guidelines. But in real life some asynchronous APIs are doing all the work during the creation of the Task, taking a lot of time, and return an already completed task (task.IsCompleted == true). This is not a good behavior, but as a consumer of the API you can't do much about it. The bad behavior is baked into the API's implementation. What you can do is to offload the misbehaving methods to the ThreadPool, so that they block thread-pool threads instead of the current thread. The easiest way to do it is the Task.Run method:

public async Task DoSomething()
{
    Task<Result1> result1Task = Task.Run(() => GetResult1Async());
    Task<Result2> result2Task = Task.Run(() => GetResult2Async());
    await Task.WhenAll(result1Task, result2Task);
    // ...
}

The Task.Run invokes the asynchronous lambda on the ThreadPool, and returns a proxy task that represents both the completion of the invocation, and the completion of the asynchronous operation.

In case your application is ASP.NET, be aware that offloading work to the ThreadPool with Task.Run might hurt the scalability of your application.

You can kick off some background work by awaiting Task.Run, but there’s no point in doing so. In fact, that will actually hurt your scalability by interfering with the ASP.NET thread pool heuristics. If you have CPU-bound work to do on ASP.NET, your best bet is to just execute it directly on the request thread. As a general rule, don’t queue work to the thread pool on ASP.NET.

As a side note, the DbContext is not thread-safe, and it doesn't support concurrent operations. If you are aiming at concurrency, you should instantiate a dedicated DbContext for each operation.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
1

Not all database providers provide truly asynchronous calls. But the idea of entity framework is to make your code independent of database provider. As a result of this is that sometimes asynchronous calls are really synchronous. I know at least some older providers for MySQL has this problem. So check the documentation for your database provider, or just update the database driver.

There are also issues with the way the code is structured, as mentioned in the comments. Just because an async API is provided does not mean the API supports concurrency.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Among other drivers, the [Oracle driver](https://stackoverflow.com/questions/29016698/can-the-oracle-managed-driver-use-async-await-properly) was known to implement the asynchronous APIs synchronously, but they might have fixed it. – Theodor Zoulias Jul 24 '23 at 10:44