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.