In this article, we are going to learn the differences between two key techniques of parallel programming – Asynchronous programming and Multithreading. More importantly, we are going to learn the use cases for both techniques.
Let’s start with a quick reminder about Asynchronous programming and Multithreading in .NET.
Asynchronous Programming
Asynchronous programming is a form of parallel programming where a set of statements run independently of the main programming flow.
We use asynchronous programming when we have a blocking operation in the program and we want to continue with the execution of the program without waiting for the result. This allows us to implement tasks that can run at the same time.
In C#, asynchronous programming is achieved through the use of the async
and await
keywords. You can learn more about this in our Asynchronous Programming with Async and Await in ASP.NET Core article.
Multithreading
In computer science, a thread is a single continuous flow of control within a program. Multithreading is a technique where the processor uses multiple threads to execute multiple processes concurrently.
We mainly use multithreading when we want to maximize the multi-core processors to have multiple workers working independently.
In C#, the System.Threading
namespace contains multiple classes and interfaces for achieving multithreading in the application. To learn more about how to run code in a new thread, you can read our How to Run Code in a New Thread in C# article.
Asynchronous Programming vs Multithreading
It 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.
Multithreading is a way of asynchrony in programming but we can also have single-threaded asynchronous tasks.
The best way to see the difference is with an example.
Async Code Example
Let’s see multiple asynchronous operations in action:
public static async Task FirstAsync() { Console.WriteLine("First Async Method on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); await Task.Delay(1000); Console.WriteLine("First Async Method Continuation on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); } public static async Task SecondAsync() { Console.WriteLine("Second Async Method on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); await Task.Delay(1000); Console.WriteLine("Second Async Method Continuation on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); } public static async Task ThirdAsync() { Console.WriteLine("Third Async Method on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); await Task.Delay(1000); Console.WriteLine("Third Async Method Continuation on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); }
In each of these asynchronous methods, we write the thread id this method uses when starting the execution. Then, we simulate some work by adding a one-second delay, and finally, we print another message to the console.
Now, let’s add another method that will execute these methods:
public static async Task ExecuteAsyncFunctions() { var firstAsync = FirstAsync(); var secondAsync = SecondAsync(); var thirdAsync = ThirdAsync(); await Task.WhenAll(firstAsync, secondAsync, thirdAsync); }
Finally, we are going to modify the Main
method:
static async Task Main(string[] args) { await ExecuteAsyncFunctions(); }
Let’s run our app and inspect the output:
First Async Method on Thread with Id: 1 Second Async Method on Thread with Id: 1 Third Async Method on Thread with Id: 1 Third Async Method Continuation on Thread with Id: 4 Second Async Method Continuation on Thread with Id: 8 First Async Method Continuation on Thread with Id: 11
We can see that all the operations are starting on the same thread with a number 1. But they are continuing their execution on different threads (4, 8, 11).
So, why is this happening?
It’s happening because once the thread hits the awaiting operation in FirstAsync
, the thread is freed from that method and returned to the thread pool. Once the operation is completed and the method has to continue, a new thread is assigned to it from a thread pool. The same process is repeated for the SecondAsync
and ThirdAsync
as well.
Multithreading Code Example
Now let’s try to implement the same in a multithreaded environment:
public class Multithreading { public void FirstMethod() { Console.WriteLine("First Method on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); Console.WriteLine("First Method Continuation on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); } public void SecondMethod() { Console.WriteLine("Second Method on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); Console.WriteLine("Second Method Continuation on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); } public void ThirdMethod() { Console.WriteLine("Third Method on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(1000); Console.WriteLine("Third Method Continuation on Thread with Id: " + Thread.CurrentThread.ManagedThreadId); } }
Also, we need to execute these methods:
public void ExecuteMultithreading() { Thread t1 = new Thread(new ThreadStart(FirstMethod)); Thread t2 = new Thread(new ThreadStart(SecondMethod)); Thread t3 = new Thread(new ThreadStart(ThirdMethod)); t1.Start(); t2.Start(); t3.Start(); }
Finally, we have to modify the Main
method:
static async Task Main(string[] args) { await ExecuteAsyncFunctions(); Console.WriteLine(); Multithreading multithreading = new Multithreading(); multithreading.ExecuteMultithreading(); }
Let’s see the output to clearly understand how they are different from each other:
First Async Method on Thread with Id: 1 Second Async Method on Thread with Id: 1 Third Async Method on Thread with Id: 1 Third Async Method Continuation on Thread with Id: 4 Second Async Method Continuation on Thread with Id: 8 First Async Method Continuation on Thread with Id: 11 First Multithreading Method on Thread with Id: 14 Second Multithreading Method on Thread with Id: 15 Third Multithreading Method on Thread with Id: 16 Second Multithreading Method Continuation on Thread with Id: 15 First Multithreading Method Continuation on Thread with Id: 14 Third Multithreading Method Continuation on Thread with Id: 16
We can clearly see the execution of multithreaded methods on different threads as expected. But also, they are keeping the same threads for the continuation compared to the async methods.
From this example, we can see the main difference – Multithreading is a programming technique for executing operations running on multiple threads (also called workers) where we use different threads and block them until the job is done. Asynchronous programming is the concurrent execution of multiple tasks (here the assigned thread is returned back to a thread pool once the await keyword is reached in the method).
Use Cases
Now that we understand the difference between multithreading and asynchronous programming, lets’s discuss the use cases for both of them.
When performing blocking operations between the methods, we should use asynchronous programming. In scenarios where we have a fixed thread pool or when we need vertical scalability in the application, we use asynchronous programming.
When we have independent functions performing independent tasks, we should use multithreading. One good example of this will be downloading multiple files from multiple tabs in a browser. Each download will run in its own thread.
Conclusion
In this article, we have discussed the key differences between asynchronous programming and multithreading. Both techniques are very useful in improving the scalability and performance of the application.
I still don’t get it. In the first example you start with one thread, and end up with three threads. In the second example you start with three threads and end with three threads. How is the first not multi-threaded if you end up with multiple threads in the end? In your conclusion you’re saying “the assigned thread is returned back to a thread pool” implying that there are multiple threads involved?
Hello Mats. Of course, both examples use multiple threads. As you wrote the last sentence, in the first example, with async the initial thread is always returned to the thread pool (when your code is waiting for some other execution), and then after a new thread is assigned to that request to finish the process. That’s why you see thread 1 for all three requests and different thread numbers once these requests are done. So, both examples are multithreaded, just in the first case, the thread is returned to the thread pool and that same thread can be assigned to another request while the first request is waiting for other executions.
Thank you, I was discussing this with a colleague who insisted that async did not use multiple threads, which is one way to interpret articles like this discussing the differences. My takeaway is rather that with async you don’t control the spawned threads you only get their results whereas with multi-threading you keep control the whole time. There are always multiple threads involved, the difference is how many are “yours”. Is that a fair conclusion?
Well, the most important fact here is that with async you can handle multiple requests at the same time, just if any of those requests reach a blocking operation, they don’t block the used thread, instead, the thread is returned for some other request to use it. But once the blocking operation is done a new thread is assigned to the waiting request so it could be finished (that’s why you see thread 1 as an entry and thread 11,18… as output – this is why I said that async is using multiple threads in my previous reply). That’s the main point of async operations.
Thank you, I think I get it. Async spawns processes more of “on demand” rather than preemptively; i.e. if no blocking operation is encountered then no process is spawned? So a loop with say 100 function calls where only 3 of them get blocked would in essence only use 4 threads instead of 101 if it had been coded as multi-threaded?
Thank you for your thorough explanation. This really illuminated the differences between the two for me, which is something I’ve struggled with. Cheers!
This only works with console applications. Maybe u should explain SynchronizationContexts. Would be kinda nice ngl.
IMO, the type of application is irrelevant here. The concept is what matters. Also, we talked a bit about synchronization context here: https://code-maze.com/asynchronous-programming-with-async-and-await-in-asp-net-core/
But as you will see, we don’t have it in the ASP.NET Core apps.
hi there, good article! I am curious about when the Thread in the Multithreading can be released (freed)? In some production cases, we can get an exception like not enough Thread in the Thread pool to complete the operation. So I would like to know the best way to manage it.
Hello Bac Vo. Thanks. Regarding your question, maybe you can find some answers here
Why did i get this article as recommendation by Android chrome? I am not a C# dev and in nodejs we have async operations and we are only using one thread. But still I used multi threading in C++. So what is the big deal?
Hey Aras, you know, you don’t have to visit the links Google recommends, right? 🙂 As for why it’s recommended to you, not sure we’re the right ones to answer that.
Mariko ..great attempt to explain async and multi thread.
Can you pls clarify below.
a) when the code hits await. , the thread is released and the method called with await executes on its own and not in the original thread where the method with await is called? Where does the method with await gets executed then? Does system handle the execution without a thread internally?
b)the thread releases the execution of method with await and moves to handle the next line of code after the await operation ?
c)can you please explain the flow of execution on how we got this ‘Seconds taken for multithreading operation: 0’? Why would it print after first thread and not after say second or third ? I think I understand but would help if you clarify
Great job again
Hello. Thank you very much for the kind words. To answer a and b questions, it is best for you to read our linked article about async and await. I will paste the link here again https://code-maze.com/asynchronous-programming-with-async-and-await-in-asp-net-core/
For the last question, no it does not print after the first thread. All the operations run in parallel for multithreading so it means they will independently write their results to the console as soon as they are finished. You can call a Sleep method before that Console write operation and you will see the order of messages will change
Nice article, there is room to explain more clearly. While reading the explanation I was feeling both does same thing until I saw the code output, may be start with code output and then your explanation would have been effective and then I wouldn’t needed to read it again.
Thank you for the comment. We’ve updated the explanations so I believe it should be more understandable now. Of course, having the code snippets and the resulting output is always helpful since you can see the difference in practice.
Quote..”Multithreading is an asynchronous operation…”
Not sure that is true ..would the operation running on the thread not need to be “marked” async to be an asynchronous operation?.. otherwise it’s just a normal thread blocking operation.
if I create 4 threads to write four separate large files, those threads are consumed until the job is done.. if my pool of threads is only 4.. then nothing else can run while these 4 threads wait. No matter how many more tasks I have.
Nowif I mark those operations asynchronous then the threads will all start and run, and be release back to the thread pool once the io starts…until it finished.. and at that point each operation will be assigned a thread and continue..in the meantime, other work can be done.. because the thread pool is not consumed..
That is the difference.. this article isn’t quite clear about.
>There is overhead to creating threads as well, may be mention.. not every single small task should be it’s own thread..
My thoughts…
Hello Dave. First of all, thank you for the comment. The first part was fixed, it was meant to sound like a form of async programming, but we modify it anyway. Regarding the second point, I completely support your explanation, but I still think we explained the same. We’ve stated that if the operation is not asynchronous it will be blocked until the job is done, but if it is the thread will be released as soon as the operation begins. Again I agree completely with your explanation and we will see to update our explanation to be even more understandable.
One more time, thanks for the comment. Comments like yours can only make the article better.
So… they’re the same. Got it.
Hello there. Any explanation why do you think that way? I believe we’ve clearly stated what is the difference between the two with examples and explanations – but I would always like to hear your thoughts.