Reputation: 3113
I created following code:
using System;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
static void Main()
{
Console.WriteLine("M Start");
MyMethodAsync();
Console.WriteLine("M end");
Console.Read();
}
static async Task MyMethodAsync()
{
await Task.Yield();
Task<int> longRunningTask = LongRunningOperationAsync();
Console.WriteLine("M3");
//and now we call await on the task
int result = await longRunningTask;
//use the result
Console.WriteLine(result);
}
static async Task<int> LongRunningOperationAsync()
{
await Task.Delay(1000);
return 1;
}
}
}
The OutPut:
M Start
M end
M3
1
Which is fine but when I look in the Thread profiler its shows this:
And then this:
And then this:
So it looks like I spawn threads, but the Microsoft documentation says:
From Task asynchronous programming model - Threads
The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.
Am I missing or don't understanding something? Thanks.
Upvotes: 18
Views: 14688
Reputation: 9
In asynchronous programming, when an await
operation is encountered inside an async block of code, it allows the control to return to the caller while the awaited task is being executed.
This frees up the thread to perform other tasks instead of waiting for the awaited task to complete.
When the awaited task finishes, it resumes execution of the async block from where it left off. This mechanism enables efficient use of resources, particularly in scenarios where waiting for certain operations (like IO-bound tasks) would otherwise block the thread.
Under the hood, a thread pool manages the execution of tasks, including those awaited within async operations. When an awaited task completes, the thread pool assigns a thread to continue executing the code after the await
. This helps in achieving concurrency and responsiveness in applications without unnecessarily tying up threads while waiting for tasks to complete.
static async void Method3()
{
MetaInfoHelper("Method 3");
Console.WriteLine("Method 3 is about to execute ....");
await Task.Delay(5000);
MetaInfoHelper("Method 3");
Console.WriteLine("Method 3 completed execution.");
MetaInfoHelper("Method 3");
}
static Task MetaInfoHelper(string methodName)
{
Console.WriteLine($"{methodName} IsThreadPoolThread : {Thread.CurrentThread.IsThreadPoolThread}, ManagedThreadId : {Thread.CurrentThread.ManagedThreadId} , IsBackground : {Thread.CurrentThread.IsBackground}");
}
[Detail1] Further detail of the behavior is driven by the synchronization context the code is running on at the moment await is executed. This answer describes Console behavior, (what was asked for by the question) but on different synchronization contexts everything after await might continue on the same thread (UI context, certain scenarios in Asp.Net), or at the thread pool, what is the default synchronization context for console.
[Detail2] If await is completed right away, it does nothing, code continues synchronously on the same thread/task/ExecutionContexted it entered await.
Upvotes: 0
Reputation: 43981
The async and await keywords don't cause additional threads to be created.
Yes, the async
and await
keywords themselves don't cause the creation of additional threads. This is true, though the asynchronous method whose Task
is awaited might cause the creation of new threads. This is an implementation detail, and you as the consumer of the asynchronous method can't do anything about it. For example an asynchronous method can be implemented like this:
Task DeleteFileAsync(string path)
{
TaskCompletionSource tcs = new();
new Thread(() => { File.Delete(path); tcs.SetResult(); }).Start();
return tcs.Task;
}
...which obviously creates a new thread, and does the assigned work on that thread. The author of a C# method is absolutely free to implement it however they like, and the async
/await
doesn't limit their freedom in any way. What Microsoft's article says is that await
ing this method won't create additional threads, i.e. additional to the threads that the asynchronous method might have already created by itself.
Async methods don't require multithreading because an async method doesn't run on its own thread.
This statement in Microsoft's documentation is misleading, and its terminology is confusing (what "require multithreading" even means?). In general it is true that most built-in asynchronous methods do not run on CPU threads for most of their lifetime, and instead they run on other devices like network cards or disk drivers. How this works is explained in detail in Stephen Cleary's famous article There is no thread. But even the purest asynchronous method has to use a thread in order to signal its completion, there is no way around it. A network card can't execute the tcs.SetResult()
line by itself. And when you consume an asynchronous method in an environment that lacks an ambient SynchronizationContext
, like a console application, the completing thread will emerge as the thread that runs the continuation after the await
keyword. So if you await
the aforementioned DeleteFileAsync
method in a console application, after the await
your code will be running on the thread that was started inside this method, exposing in some way its internal implementation.
Another question that you might find interesting is: What thread runs the code after the await
keyword?
Upvotes: 1
Reputation: 62498
The async and await keywords don't cause additional threads to be created.
Yes. It moves the CPU bound or I/O bound work to other thread from the thread pool of the process so that it is not executed on UI thread or current synchronization context, it does not create a new thread which is what meant in the MSDN description.
Upvotes: 1
Reputation: 457382
I explain how async
and await
work with threads and contexts on my blog. In summary, when await
needs to wait for an asynchronous operation to complete, it will "pause" the current async
method and (by default) capture a "context".
When the asynchronous operation completes, that "context" is used to resume the async
method. This "context" is SynchronizationContext.Current
, unless it is null
, in which case it is TaskScheduler.Current
. In your case, the context ends up being the thread pool context, so the rest of the async
method is sent to the thread pool. If you run the same code from the UI thread, the context would be the UI context, and all the async
methods will resume on the UI thread.
Upvotes: 19