Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 756
Flack Avatar asked May 15 '17 21:05

Flack


People also ask

What is the difference between threading and asynchronous?

Threading is about workers; asynchrony is about tasks. In asynchronous single-threaded workflows you have a graph of tasks where some tasks depend on the results of others; as each task completes it invokes the code that schedules the next task that can run, given the results of the just-completed task.

What happens if we execute an asynchronous method but do not await it?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Is asynchronous same as multithreading?

Asynchronous Programming vs MultithreadingIt is a general misconception that both asynchronous programming and multithreading are the same although that's not true. Asynchronous programming is about the asynchronous sequence of Tasks, while multithreading is about multiple threads running in parallel.

Are threads asynchronous?

There are two ways to create threads: synchronous threading - the parent creates one (or more) child threads and then must wait for each child to terminate. Synchronous threading is often referred to as the fork-join model. asynchronous threading - the parent and child run concurrently/independently of one another.


3 Answers

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.

like image 191
Eric Lippert Avatar answered Nov 15 '22 21:11

Eric Lippert


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".

like image 40
Stephen Cleary Avatar answered Nov 15 '22 23:11

Stephen Cleary


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.

This simply means there are two cases when calling an async method.

The first is that, upon returning the task to you, the operation is already completed -- this would be a synchronous path. The second is that the operation is still in progress -- this is the async path.

Consider this code, which should show both of these paths. If the key is in a cache, it is returned synchronously. Otherwise, an async op is started which calls out to a database:

Task<T> GetCachedDataAsync(string key)
{
    if(cache.TryGetvalue(key, out T value))
    {
        return Task.FromResult(value); // synchronous: no awaits here.
    }
    
    // start a fully async op.
    return GetDataImpl();
    
    async Task<T> GetDataImpl()
    {
        value = await database.GetValueAsync(key);
        cache[key] = value;
        return value;
    }
}

So by understanding that, you can deduce that in theory the call of database.GetValueAsync() may have a similar code and itself be able to return synchronously: so even your async path may end up running 100% synchronously. But your code doesn't need to care: async/await handles both cases seamlessly.

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.

Blocking is a well-defined term -- it means your thread has yielded its execution window while it waits for something (I/O, mutex, and so on). So your thread doing the math is not considered blocked: it is actually performing work.

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?

A "truly async method" would be one that simply never blocks. It typically ends up involving I/O, but it can also mean awaiting your heavy math code when you want to your current thread for something else (as in UI development) or when you're trying to introduce parallelism:

async Task<double> DoSomethingAsync()
{
    double x = await ReadXFromFile();
    
    Task<double> a = LongMathCodeA(x);
    Task<double> b = LongMathCodeB(x);
    
    await Task.WhenAll(a, b);
    
    return a.Result + b.Result;
}
like image 32
Cory Nelson Avatar answered Nov 15 '22 23:11

Cory Nelson