Reputation: 390
Note: Please read to the end before marking as duplicate. I've read the other answers, and they don't seem to answer my question.
I've seen various pictures and people point out and say that multithreading is different from asynchronous programming, by giving various analogies to restaurant workers and the like. But I've yet to see the difference with an actual example.
I tried this in C#:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTest
{
class Program
{
static void RunSeconds(double seconds)
{
int ms = (int)(seconds * 1000);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine($"Thread started to run for {seconds} seconds");
Thread.Sleep(ms);
stopwatch.Stop();
Console.WriteLine($"Stopwatch passed {stopwatch.ElapsedMilliseconds} ms.");
}
static async Task RunSecondsAsync(double seconds)
{
int ms = (int)(seconds * 1000);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
Console.WriteLine($"Thread started to run for {seconds} seconds");
await Task.Run(() => Thread.Sleep(ms));
stopwatch.Stop();
Console.WriteLine($"Stopwatch passed {stopwatch.ElapsedMilliseconds} ms.");
}
static void RunSecondsThreaded(double seconds)
{
Thread th = new Thread(() => RunSeconds(seconds));
th.Start();
}
static async Task Main()
{
Console.WriteLine("Synchronous:");
RunSeconds(2.5); RunSeconds(2);
Console.WriteLine("\nAsynchronous:");
Task t1 = RunSecondsAsync(2.5); Task t2 = RunSecondsAsync(2);
await t1; await t2;
Console.WriteLine("\nMultithreading:");
RunSecondsThreaded(2.5); RunSecondsThreaded(2);
}
}
}
Results:
Synchronous:
Thread started to run for 2.5 seconds
Stopwatch passed 2507 ms.
Thread started to run for 2 seconds
Stopwatch passed 2001 ms.
Asynchronous:
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2002 ms.
Stopwatch passed 2554 ms.
Multithreading:
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2000 ms.
Stopwatch passed 2501 ms.
They yielded essentially the same results, behaviour-wise. So when and what exactly would I find different in the behaviour of a multithreaded program vs an asynchronous one?
I have various other issues to resolve:
In this image, for example:
What I don't get is that when you run an asynchronous program, it behaves practically identically to a multithreaded one, in that it seems to spend a similar amount of time. By the image above, it's addressing the asynchronous task in "breaks". If it does this, shouldn't it take longer for the asynchronous task to complete?
Let's say an asynchronous task which would normally complete 3 seconds synchronously while locking other tasks is run, should I not expect these tasks to finish in much longer than 3 seconds, given that it does other tasks on the side while taking breaks from my original task?
So why does it often take a similar asynchronously (ie. the usual 3 seconds)? And why does the program become "responsive": if the task is not being done on a separate thread, why does working on the task while working on other tasks on the side take only the expected 3 seconds?
The problem I have with the examples using workers in a restaurant (see top answer), is that in a restaurant, the cooking is done by the oven. In a computer, this analogy doesn't make much sense, as it's not clear why the oven isn't being treated as a separate "thread" but the people/workers are.
Furthermore, does a multithreaded application use more memory? And if it does, is it possible to create a simple application (ideally as similar to the one above) proving that it does?
Bit of a lengthy question, but the differences between multithreading and asynchronous programming are far from clear to me.
Upvotes: 3
Views: 1540
Reputation: 989
Let me try to correlate your program with a real world example and then explain it.
Consider your program to be an IT office and your are the boss of it. Boss means the main thread which starts the program execution. The console can be considered as your diary.
Programs execution starts:
static async Task Main()
{
Process process = Process.GetCurrentProcess();
Console.WriteLine("Synchronous:");
You enter into the office from the main door and log "Synchronous:" into your diary.
Synchronous:
Calling method 'RunSeconds()'
RunSeconds(2.5); RunSeconds(2);
Let us assume 'RunSeconds()' is equivalent to a call from one of your projects client, however there is no one to attend the calls. So you attend both the calls.The thing to remember is you attend the calls one after the other as you are one person and total spent is close to 4.5 seconds. Meanwhile you get a call from your home but you could not attend it because you were busy attending the client calls. Now coming to logging of the calls.You get a call you log it.Once it is completed you log the amount of time spent on call. And you do it twice for both the calls.
Thread started to run for 2.5 seconds
Stopwatch passed 2507 ms.
Thread started to run for 2 seconds
Stopwatch passed 2001 ms.
Console.WriteLine("\nAsynchronous:");
Then you log "Asynchronous:" into the diary
Calling method 'RunSecondsAsync()'
Task t1 = RunSecondsAsync(2.5); Task t2 = RunSecondsAsync(2);
await t1; await t2;
Let us assume 'RunSecondsAsync()' is again equivalent to a call from one of your projects client, however this time you have a Manager with a team of 10 call attendants who take the call. Here Manager is equivalent to the Task and each call attendant is a thread and collectively known as thread pool. Remember the manager by himself does not take any calls, he is just there to delegate calls to the call attendants and manage them When the first call 'RunSecondsAsync(2.5)' comes in, the manager immediately assigns it to one of the call attendant and lets you know that the call has been addressed with the help of task object as return. You again get an immediate second call 'RunSecondsAsync(2)', which the manager immediately assigns to another call attendant and both the calls are handled simultaneously. However you want to log the amount of time spent on the phone calls, so you wait for those calls to be completed with the help of await keywords. The key difference of waiting this time is, you are still free to do whatever you want because the phone calls are attended by call attendants.So if you get a call from your home this time around you will be able to take it. (analogous to application being responsive). Once the calls are done, the manager lets you know that the calls are completed and you go ahead and log in your diary. Now coming to logging of the calls, you first log both the calls which have come in and once they are completed you log in the total time spent on each call. The total duration spent by you in this case is close to 2.5 seconds which is the maximum of both calls because calls are handled in parallel and some overhead in communicating with the manager.
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2002 ms.
Stopwatch passed 2554 ms.
Console.WriteLine("\Multithreading:");
Then you log "Multithreading:" into the diary
Calling method 'RunSecondsThreaded()'
RunSecondsThreaded(2.5); RunSecondsThreaded(2);
And finally you and your manager have a small fight and he leaves the organization. However you do not want to take the calls because you have other important tasks to take care of. So you hire a call attendant when a phone call comes in and have the work done for you. You do it two times because two calls have come by. Meanwhile you are again free to do other tasks like if you get a phone call from your home you can attend it. Now coming to logging of the calls. You do not log the calls this time around into the diary. The call attendants do it on your behalf. The work done by you is just hiring the call attendants. Since calls have come in almost at the same time, the total time spent is 2.5 seconds plus some additional time for hiring.
Thread started to run for 2.5 seconds
Thread started to run for 2 seconds
Stopwatch passed 2000 ms.
Stopwatch passed 2501 ms.
Hope it helps in resolving your confusion
Upvotes: 2
Reputation: 474
You can't use Thread.Sleep
in async code, use
await Task.Delay(1000);
instead.
The async code uses a thread pool, any time the program awaits for some IO to complete, the thread is returned to the pool to do other stuff. Once the IO completes, the async method resumes at the line where it yielded the thread back to threadpool, continuing on.
When you manipulate with the Thread directly, you block and your code is no longer async, you also starve the threadpool as it is limited in the number of threads available.
Also throughout the lifetime of an async method, you are not guaranteed every line will be executed on the same thread. Generally after every await keyword the thread may change.
You never want to touch the Thread class in an async method.
By doing:
await Task.Run(() => Thread.Sleep(ms));
You force the TPL to allocate a thread out of the pool to block it, starving it. By doing
await Task.Run(async () => await Task.Delay(ms));
you will essentially run on one or two threads from a pool even if you start it many times.
Running Task.Run() on synchronous code is mostly used for legacy calls that do not support async internally and the TPL just wraps the sync call in a pooled thread. To get the full advantages of async code you need to await a call that itself runs only async code internally.
Upvotes: 4