Confusion regarding threads and if asynchronous methods are truly asynchronous in C#

I was reading up on async/await and when Task.Yield might be useful and came across this post. I had a question regarding the below from that post:

When you use async/await, there is no guarantee that the method you call when you do await FooAsync() will actually run asynchronously. The internal implementation is free to return using a completely synchronous path.

This is a little unclear to me probably because the definition of asynchronous in my head is not lining up.

In my mind, since I do mainly UI dev, async code is code that does not run on the UI thread, but on some other thread. I guess in the text I quoted, a method is not truly async if it blocks on any thread (even if it's a thread pool thread for example).

Question:

If I have a long running task that is CPU bound (let's say it is doing a lot of hard math), then running that task asynchronously must be blocking some thread right? Something has to actually do the math. If I await it then some thread is getting blocked.

What is an example of a truly asynchronous method and how would they actually work? Are those limited to I/O operations which take advantage of some hardware capabilities so no thread is ever blocked?


This is a little unclear to me probably because the definition of asynchronous in my head is not lining up.

Good on you for asking for clarification.

In my mind, since I do mainly UI dev, async code is code that does not run on the UI thread, but on some other thread.

That belief is common but false. There is no requirement that asynchronous code run on any second thread.

Imagine that you are cooking breakfast. You put some toast in the toaster, and while you are waiting for the toast to pop, you go through your mail from yesterday, pay some bills, and hey, the toast popped up. You finish paying that bill and then go butter your toast.

Where in there did you hire a second worker to watch your toaster?

You didn't. Threads are workers. Asynchronous workflows can happen all on one thread. The point of the asynchronous workflow is to avoid hiring more workers if you can possibly avoid it.

If I have a long running task that is CPU bound (let's say it is doing a lot of hard math), then running that task asynchronously must be blocking some thread right? Something has to actually do the math.

Here, I'll give you a hard problem to solve. Here's a column of 100 numbers; please add them up by hand. So you add the first to the second and make a total. Then you add the running total to the third and get a total. Then, oh, hell, the second page of numbers is missing. Remember where you were, and go make some toast. Oh, while the toast was toasting, a letter arrived with the remaining numbers. When you're done buttering the toast, go keep on adding up those numbers, and remember to eat the toast the next time you have a free moment.

Where is the part where you hired another worker to add the numbers? Computationally expensive work need not be synchronous, and need not block a thread. The thing that makes computational work potentially asynchronous is the ability to stop it, remember where you were, go do something else, remember what to do after that, and resume where you left off.

Now it is certainly possible to hire a second worker who does nothing but add numbers, and then is fired. And you could ask that worker "are you done?" and if the answer is no, you could go make a sandwich until they are done. That way both you and the worker are busy. But there is not a requirement that asynchrony involve multiple workers.

If I await it then some thread is getting blocked.

NO NO NO. This is the most important part of your misunderstanding. await does not mean "go start this job asynchronously". await means "I have an asynchronously produced result here that might not be available. If it is not available, find some other work to do on this thread so that we are not blocking the thread. Await is the opposite of what you just said.

What is an example of a truly asynchronous method and how would they actually work? Are those limited to I/O operations which take advantage of some hardware capabilities so no thread is ever blocked?

Asynchronous work often involves custom hardware or multiple threads, but it need not.

Don't think about workers. Think about workflows. The essence of asynchrony is breaking up workflows into little parts such that you can determine the order in which those parts must happen, and then executing each part in turn, but allowing parts that do not have dependencies with each other to be interleaved.

In an asynchronous workflow you can easily detect places in the workflow where a dependency between parts is expressed. Such parts are marked with await. That's the meaning of await: the code which follows depends upon this portion of the workflow being completed, so if it is not completed, go find some other task to do, and come back here later when the task is completed. The whole point is to keep the worker working, even in a world where needed results are being produced in the future.


I was reading up on async/await

May I recommend my async intro?

and when Task.Yield might be useful

Almost never. I find it occasionally useful when doing unit testing.

In my mind, since I do mainly UI dev, async code is code that does not run on the UI thread, but on some other thread.

Asynchronous code can be threadless.

I guess in the text I quoted, a method is not truly async if it blocks on any thread (even if it's a thread pool thread for example).

I would say that's correct. I use the term "truly async" for operations that do not block any threads (and that are not synchronous). I also use the term "fake async" for operations that appear asynchronous but only work that way because they run on or block a thread pool thread.

If I have a long running task that is CPU bound (let's say it is doing a lot of hard math), then running that task asynchronously must be blocking some thread right? Something has to actually do the math.

Yes; in this case, you would want to define that work with a synchronous API (since it is synchronous work), and then you can call it from your UI thread using Task.Run, e.g.:

var result = await Task.Run(() => MySynchronousCpuBoundCode());

If I await it then some thread is getting blocked.

No; the thread pool thread would be used to run the code (not actually blocked), and the UI thread is asynchronously waiting for that code to complete (also not blocked).

What is an example of a truly asynchronous method and how would they actually work?

NetworkStream.WriteAsync (indirectly) asks the network card to write out some bytes. There is no thread responsible for writing out the bytes one at a time and waiting for each byte to be written. The network card handles all of that. When the network card is done writing all the bytes, it (eventually) completes the task returned from WriteAsync.

Are those limited to I/O operations which take advantage of some hardware capabilities so no thread is ever blocked?

Not entirely, although I/O operations are the easy examples. Another fairly easy example is timers (e.g., Task.Delay). Though you can build a truly asynchronous API around any kind of "event".