Holger Thiemann
Holger Thiemann

Reputation: 1082

When using "Async" Methods are there Threads of the Threadpool blocked?

AFAIK the following is true:

It is not a good idea to block a thread of the threadpool for a long time (because they are limited). Tasks use threads of the threadpool and are therefore for short term, not long blocking actions. Async methods (like FindAsync in Entity Framework) return a task you can wait (or await) for to receive the result.

What I dont't understand:

If I call e.g. FindAsync is there simply a task created that runs on a ThreadPool thread and invokes the non async find method (blocking the ThreadPool Thread until the find returns)? Or are there deeper operating system mechanisms involved and a ThreadPool Thread is used not before the Find method Returns?

The Reason:

If variant 1 holds true, there is no difference between calling the FindAsync method or starting a task myself and calling the Find method in it.

If variant 2 holds true, there is a difference, because starting a task calling the Find method will long term block a ThreadPool Thread, while calling FindAsync will not.

Upvotes: 1

Views: 1782

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456407

It is not a good idea to block a thread of the threadpool for a long time (because they are limited).

The thread pool will respond to a "loss" of a thread by injecting a new one, so blocking a thread pool thread for a long time is not particularly problematic. (Of course, it would be better not to block a thread pool thread in the first place).

Tasks use threads of the threadpool and are therefore for short term, not long blocking actions.

This is only true of Delegate Tasks that are scheduled to the thread pool. A lot of the old (.NET 4-era) documentation around tasks assume that you're using Delegate Tasks scheduled on the thread pool, so this is a common misunderstanding of tasks in general.

In the modern world, Delegate Tasks are much more rare; Promise Tasks are more common these days. Promise Tasks just act as a "signal" meaning "something completed." For more about Delegate Tasks and Promise Tasks, see my blog posts on Task Overview and Task status.

(Also, Delegate Tasks can be scheduled to any kind of TaskScheduler; they're not just for thread pool tasks).

Async methods (like FindAsync in Entity Framework) return a task you can wait (or await) for to receive the result.

There's a distinction here that needs to be made between methods that use async for their implementation, and TAP methods that end in *Async and are intended for use with await. Those two commonly go together, but not always.

async always returns a Promise Task, never a Delegate Task. Also, it's generally expected that an *Async method will return a Promise Task, but some methods return Delegate Tasks instead. When methods do this, I call it "fake asynchrony", because it's just synchronously blocking another thread.

If I call e.g. FindAsync is there simply a task created that runs on a ThreadPool thread and invokes the non async find method (blocking the ThreadPool Thread until the find returns)?

Not in the general case. Pretty much all *Async methods provided by .NET return Promise Tasks.

Or are there deeper operating system mechanisms involved and a ThreadPool Thread is used not before the Find method Returns?

The Promise Tasks returned by most .NET *Async methods use I/O Completion Ports under the hood, as I describe in my article There Is No Thread.

If variant 1 holds true, there is no difference between calling the FindAsync method or starting a task myself and calling the Find method in it.

If variant 2 holds true, there is a difference, because starting a task calling the Find method will long term block a ThreadPool Thread, while calling FindAsync will not.

In the general case, you want to use the *Async methods, which avoid blocking any threads at all.

However, as others have noted in the comments, this particular example of Entity Framework is a bit more complex. Entity Framework itself is async-agnostic; it builds on top of "providers", which may or may not support asynchrony. Microsoft's SqlClient provider does support asynchrony, so FindAsync talking to SQL Server will properly work asynchronously. However, other providers may not (as of this writing, SQLite is a common provider that does not support asynchrony), and for those providers, "asynchronous" APIs like FindAsync are actually implemented by blocking a thread pool thread.

So, in the general case, "variant 2" would be true; but for your particular example of FindAsync, they are both true.

Upvotes: 4

Related Questions