In programs utilizing async-await, my understanding is like this:
an async method that IS NOT awaited will run in the background (?) and rest of the code will continue to execute before this non-awaited method finishes
an async method that IS awaited will wait until the method finishes before moving on to the next lines of code
The application below was written by me to check if the above statements are correct.
using System;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
DoJob();
var z = 3;
Console.ReadLine();
}
static async Task DoJob()
{
var work1 = new WorkClass();
var work2 = new WorkClass();
while (true)
{
await work1.DoWork(500);
await work2.DoWork(1500);
}
}
}
public class WorkClass
{
public async Task DoWork(int delayMs)
{
var x = 1;
await Task.Delay(delayMs);
var y = 2;
}
}
}
Here are some of my observations:
The DoJob(); call is not awaited. However, the debugger shows me that the code inside of DoJob is being executed, just as if it was a normal non-async method.
When code execution gets to await work1.DoWork(500);, I would think "OK, so maybe now the DoJob method will be left and var z = 3; will be executed? After all, 'await' should leave the method." In reality, it just goes into DoWork and doesn't leave DoJob - var z = 3; is still not executed.
Finally, when execution reaches await Task.Delay(delayMs);, DoJob is left, and the var z = 3; is reached. After that, code after the Delay is executed.
The things that I don't understand:
Why does await Task.Delay(delayMs); leave the DoJob method, but await work1.DoWork(500); does not?
I see that DoJob is executing normally. I thought it would be done in the background (maybe by one of the thread pool threads?). Looks like it could block the thread if it was some long-running method, am I right?
Why does await Task.Delay(delayMs); leave the DoJob method, but await work1.DoWork(500); does not?
Because this code:
await work1.DoWork(500);
is the same as this code:
var task = work1.DoWork(500);
await task;
So your code is calling the method first, and then awaiting the returned task. It's common to talk about await as "awaiting method calls", but that's not what actually happens - technically, the method call is done first (synchronously), and then the returned task is awaited.
I see that DoJob is executing normally. I thought it would be done in the background (maybe by one of the thread pool threads?).
No; with true asynchronous operations, there is no thread that is blocked on that operation.
Looks like it could block the thread if it was some long-running method, am I right?
Yes.
my understanding is like this
I recommend reading my async intro for a better mental framework. In summary:
async enables the await keyword. It also generates a state machine that handles creating the Task return value and stuff like that.
await operates on an "awaitable" (usually a task). First, it checks to see if it's already complete; if it is, the async method continues executing synchronously.
If the awaitable is not already complete, then await (by default) captures its context and schedules the continuation of the async method to run on that context when the awaitable completes.
The compiler splits the code in an async method in chunks. 1 before the first await and 1 between each await and 1 after the last await.
The execution will return to the caller at the first non completed awaiter or the end of the method.
This method will only return a completed Task after fully executed:
async Task M1() => await Task.CompletedTask;
This method will only return an incomplete Task that will complete when the Task returned by Task.Dealy(1000) is completed:
async Task M2() => await Task.Delay(1000);
Here's a small example:
static async Task Main(string[] args)
{
var t = TwoAwaits();
Console.WriteLine("Execution returned to main");
await t;
}
private static async Task TwoAwaits()
{
Console.WriteLine("Before awaits");
await Task.CompletedTask;
Console.WriteLine("Between awaits #1");
await Task.Delay(1000);
Console.WriteLine("Between awaits #2");
await Task.Delay(1000);
Console.WriteLine("After awaits");
}
/*
Before awaits
Between awaits #1
Execution returned to main
Between awaits #2
After awaits
*/
Let's look at the four possibilities:
(1)
void Main()
{
Console.WriteLine($"Main 0 - {Thread.CurrentThread.ManagedThreadId}");
DoJob();
Console.WriteLine($"Main 1 - {Thread.CurrentThread.ManagedThreadId}");
}
public static async Task DoJob()
{
Console.WriteLine($"DoJob 0 - {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"DoJob 1 - {Thread.CurrentThread.ManagedThreadId}");
}
This outputs:
Main 0 - 14
DoJob 0 - 14
DoJob 1 - 14
Main 1 - 14
It has a 2 second pause after DoJob 0.
(2)
async Task Main()
{
Console.WriteLine($"Main 0 - {Thread.CurrentThread.ManagedThreadId}");
await DoJob();
Console.WriteLine($"Main 1 - {Thread.CurrentThread.ManagedThreadId}");
}
public static async Task DoJob()
{
Console.WriteLine($"DoJob 0 - {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"DoJob 1 - {Thread.CurrentThread.ManagedThreadId}");
}
Again this outputs:
Main 0 - 14
DoJob 0 - 14
DoJob 1 - 14
Main 1 - 14
(3)
async Task Main()
{
Console.WriteLine($"Main 0 - {Thread.CurrentThread.ManagedThreadId}");
await DoJob();
Console.WriteLine($"Main 1 - {Thread.CurrentThread.ManagedThreadId}");
}
public static Task DoJob()
{
return Task.Run(() =>
{
Console.WriteLine($"DoJob 0 - {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"DoJob 1 - {Thread.CurrentThread.ManagedThreadId}");
});
}
This has different output because it has changed thread:
Main 0 - 15
DoJob 0 - 13
DoJob 1 - 13
Main 1 - 13
And finally:
async Task Main()
{
Console.WriteLine($"Main 0 - {Thread.CurrentThread.ManagedThreadId}");
DoJob();
Console.WriteLine($"Main 1 - {Thread.CurrentThread.ManagedThreadId}");
}
public static Task DoJob()
{
return Task.Run(() =>
{
Console.WriteLine($"DoJob 0 - {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(2000);
Console.WriteLine($"DoJob 1 - {Thread.CurrentThread.ManagedThreadId}");
});
}
This has a different output again:
Main 0 - 13
Main 1 - 13
DoJob 0 - 12
DoJob 1 - 12
In this last case it is not waiting for DoJob because DoJob is running on a different thread.
So if you follow the logic here the issue is that async/await doesn't create (or use) a different thread. The method called must do that.
Before Async & Await, there was two type of methods. Those who returned the result directly, and those who received a callback function as a parameter. On the latter, the method was invoked in the same thread syncronously and returned no value, and later, on the same or different thread your callback function would have been called with the result. Historically all I/O (disk, network, even memory) worked with callbacks (actually: interrupts) but medium to high level languages like C# would mask all that internally so end users don't need to learn/write low level code.
This worked pretty well up to a point, except this optimization wasted some physical resources. For example Node.js outperformed several other languages/server platforms by their limitation that forces the developers to use the callback model instead of the 'managed' mode.
This pushed C# and other languages to go back to the callback model, but the code readability really suffered (code callback spaguetti). So Async and Await was introduced.
Async and await let's you write in the 'callback model' with 'managed' syntax. All callbacks are handled by the compiler.
Each time you write 'await' in an async method, your method is actually split into two methods connected by a callback.
Now, you can write an async method that does regular sync code, without awaits, nor thread switch or I/O. That 'async' method will actually run synchronously. So, it is actually the same to await method1() or call without await. Why? because your async call is not awaiting anything, so your async code is still one piece of continous code.
If inside your method you await one, two or more different methods, then your method will be split into one, two or more pieces. And only the first piece will be guaranteed to be run synchronously. All the other pieces will run on other thread depending on the code that you are awaiting.
TL;DR;
Async/Await method is does not guarantees multi-threading or parallel processing. That will actually depend on the payload (the called async method). For example, http downloads will tipically be paralellized if you manage your awaits because those are functions that are mostly waiters of an external response. On the other side, heavy CPU processing, like compressing a file, will require other form of cpu/thread management not provided by async/await.
If you do not await an async method, your code will surely run synchronously up to the first await of the called method, given it has one. But later on, it may or not run sync.
why does await Task.Delay(delayMs); leave the DoJob method, but await work1.DoWork(500); does not?
Because, up and until there is an actual asynchronous call, it's still in the same context. If DoWork was just:
public async Task DoWork(int delayMs)
{
var x = 1;
var y = 2;
return Task.CompletedTask;
}
there would be no need for a continuation and hence, you would debug all the way without "jumping" back to the orignal await call.
Here is how your application could be remodeled if you were forced to avoid async/await for some reason. See how complex it gets to replicate the logic inside the loop. Async/await is really a gift from the heavens!
using System;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
static Task Main(string[] args)
{
Console.WriteLine("Hello World!");
DoJob();
var z = 3;
Console.ReadLine();
return Task.CompletedTask;
}
static Task DoJob()
{
var work1 = new WorkClass();
var work2 = new WorkClass();
var tcs = new TaskCompletionSource<bool>();
Loop();
return tcs.Task;
void Loop()
{
work1.DoWork(500).ContinueWith(t1 =>
{
if (t1.IsFaulted) { tcs.SetException(t1.Exception); return; }
work2.DoWork(1500).ContinueWith(t2 =>
{
if (t2.IsFaulted) { tcs.SetException(t2.Exception); return; }
if (true) { Loop(); } else { tcs.SetResult(true); }
// The 'if (true)' corresponds to the 'while (true)'
// of the original code.
});
});
}
}
}
public class WorkClass
{
public Task DoWork(int delayMs)
{
var x = 1;
int y;
return Task.Delay(delayMs).ContinueWith(t =>
{
if (t.IsFaulted) throw t.Exception;
y = 2;
});
}
}
}
Related
I just have some basic understanding of C#'s asynchronous programming. As I understand, this code
statement1;
await statement2();
statement3;
statement4;
should logically be equivalent to
statement1;
var awaiter = statement2().GetAwaiter();
awaiter.OnCompletion(() => {
awaiter.GetResult();
statement3;
statement4;
});
So if there is an await statement in the code, all the following statements will only start to execute after the awaited task completed. This seems just like synchronous code. The document states that the await statement will cause the execution return to the caller and resume after the awaited task completes. But I can't fully understand how it works in the following example.
using System;
using System.Threading.Tasks;
namespace ThreadTesting
{
class Program
{
public static async Task Main(string[] args)
{
await DoSomething2(); // <------- B
Console.WriteLine("Test!"); // <------- C
Console.ReadKey(); // <------- D
}
public static async Task DoSomething2()
{
Console.WriteLine("Start DoSomething2");
var i = await DoSomething(); // <------ A
Console.WriteLine("End DoSomething2");
}
public static async Task<int> DoSomething()
{
Console.WriteLine("Start DoSomething1");
await Task.Delay(10000);
Console.WriteLine("Before returning from DoSomething1");
return 88;
}
}
}
The output is
Start DoSomething2
Start DoSomething1
Before returning from DoSomething1
End DoSomething2
Test!
Let's take statement A for example. Here it's awaiting DoSomething(). While awaiting it, the execution logic returns from DoSomething2() and goes back to B. Here as I understand, the execution should go on to execute statement C and D while awaiting DoSomething2() (statement B). But the result shows that the string "Test!" only be printed at the end of the program. Why this happens? Is my understanding correct?
The catch is that your Main returns Task, so it is also fully async and will be "stopped". And your execution order will look like this:
Main() starts
Main gets to the await operator and creates a Task with the rest of the method. It looks as if the Main() has stopped, waiting for the await result
DoSomething2() starts and writes to the console "Start DoSomething2"
It gets to the next await operator and creates another Task with the rest of the DoSomething() method
DoSomething() starts
It writes to the console "Start DoSomething1"
Gets to the awat operator and creates a Task with the rest of the DoSomething() method, waiting for a delay.
After this Task is completed (after a delay) DoSomething() writes "Before returning from DoSomething1" to console and returns 88.
The Task, created in DoSomething2() completes the rest of the method (assigns 88 to i and writes "Before returning from DoSomething1" to console
The Task, created in Main() completes, writing "Test!" to console.
So the "Test!" is in the end because your Main() returns Task. If you want the behaviour, you expect just swap Task to void:
public static async void Main(string[] args)
{
...
}
Then when reaching the await operator, the Main() wont wait for the Task() completion and will continue the execution.
Something worked like this..
You have to go through all function then back to where call await for it.
using System;
using System.Threading.Tasks;
namespace ThreadTesting
{
class Program
{
public static async Task Main(string[] args)
{
await DoSomething2();// <------ Start go to DoSomething2
Console.WriteLine("Test!"); //F
Console.ReadKey();
}
public static async Task DoSomething2()
{
Console.WriteLine("Start DoSomething2"); // <------ A godown
var i = await DoSomething(); // <------ B go to DoSomething
Console.WriteLine("End DoSomething2");// <------ E back to Main
}
public static async Task<int> DoSomething()
{
Console.WriteLine("Start DoSomething1");// <------ C godown
await Task.Delay(10000);
Console.WriteLine("Before returning from DoSomething1");// <------ D back to DoSomething2
return 88;
}
}
}
await and async do stop the execution of the code for you. But they actually do not stop the execution of the main thread.
In your example, everything looks like working as expected.
Here as I understand, the execution should go on to execute statement C and D while awaiting DoSomething2()
No, they shouldn't because you said you want to wait till it is done. Even if it does not return something, it will wait because of await. Inside DoSomething2() you do the same thing.
await is more reasonable when you depend on the result of an asynchronous function. It saves you from callback hell.
Instead of this
you can use this
note: screenshots are from https://blog.hellojs.org/asynchronous-javascript-from-callback-hell-to-async-and-await-9b9ceb63c8e8 The codes are in JS.
As canbax already told in his answer the await operator does excatly what its naming suggests. It Asynchronously waits that the Task (or better any type that can be awaited) completes.
So if you have code like
DoSomething1();
await DoSomething2Async();
DoSomething3();
the call to DoSomething3 will only ocure after the task returned by the call to DoSomething2Async has finished with out errors, regardless of what happens in DoSomething2Async
In contrast you can creat a task by calling an async method and only later await the task, like:
DoSomething1();
Task task = DoSomething2Async();
DoSomething3();
await task;
this code is more open to the order of calling DoSomething3 and the completion of the task. This is because you basicly tell the compiler "start doing DoSomething2Async, but I only care that it finishes after the call to DoSomething3 has finished.
Here is a live example for
using System;
using System.Threading.Tasks;
class Program {
static async Task Main(string[] args) {
DoSomething1();
await DoSomething2Async();
DoSomething3();
Console.WriteLine("---");
DoSomething1();
var task = DoSomething2Async();
DoSomething3();
await task;
}
static void DoSomething1()
{
Console.WriteLine("1");
}
static async Task DoSomething2Async()
{
await Task.Run(() => Console.WriteLine("2"));
}
static void DoSomething3()
{
Console.WriteLine("3");
}
}
which will mostlikly produce output like
1
2
3
---
1
3
2
I found the issue with my code. Just as I described in the question, this code
statement1;
await statement2();
statement3;
statement4;
roughly equivalent to
statement1;
var awaiter = statement2().GetAwaiter();
awaiter.OnCompletion(() => {
awaiter.GetResult();
statement3;
statement4;
});
This is true. So for the code
await DoSomething2(); // <------- B
Console.WriteLine("Test!"); // <------- C
Console.ReadKey(); // <------- D
it roughly equivalent to
statement1;
var awaiter = DoSomething2().GetAwaiter(); // <------- B
awaiter.OnCompletion(() => {
awaiter.GetResult();
Console.WriteLine("Test!"); // <------- C
Console.ReadKey(); // <------- D
});
That is to say, the statement C & D will only execute after the task DoSomething2() completed. That's why Test! will only be printed at the end of the program. If I remove the await before DoSomething2():
using System;
using System.Threading.Tasks;
namespace ThreadTesting
{
class Program
{
public static void Main(string[] args)
{
DoSomething2(); // <------- B (await removed)
Console.WriteLine("Test!"); // <------- C
Console.ReadKey(); // <------- D
}
public static async Task DoSomething2()
{
Console.WriteLine("Start DoSomething2");
var i = await DoSomething(); // <------ A
Console.WriteLine("End DoSomething2"); // <------ I
}
public static async Task<int> DoSomething()
{
Console.WriteLine("Start DoSomething1"); // <------ E
await Task.Delay(10000); // <------ F
Console.WriteLine("Before returning from DoSomething1"); // <------ G
return 88; // <------ H
}
}
}
The program will return this result:
Start DoSomething2
Start DoSomething1
Test!
Before returning from DoSomething1
End DoSomething2
This is reasonable and is exactly what I want. The execution logic can be logically understood as:
Start at main and run DoSomething2()
Go to DoSomething2 and print "Start DoSomething2"
Execute DoSomething() (statement A) and print "Start DoSomething1" (statement E)
Execute Task.Delay(10000) (statement F). As it takes some time and returns a task (an awaitable), add the following statements (statement G & H) as a continuation (C1) and return to statement A.
As statement A is an await statement, it adds the following statement (statement I) as a continuation (c2) and returns to statement B.
The execution goes to B and the thread is not blocked. The code continue to execute statement C and D.
Once statement F completed, the continuation C1 starts to execute.
Once C1 completed, the continuation C2 starts to execute.
This is my understanding of the logic. Please correct me if you find any error. I'm new to asynchronous programming.
The misconception I have is that I though I have to await all async methods. This is not true.
Can anybody explain why this code simply hits a dead end after the WhenAll fires?
Main code:
class AsyncTests
{
public async void Start()
{
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - starting whole process, calling await DoWork1()");
await Task.WhenAll(DoWork1(), DoWork2());
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - finished awaiting DoWork1 and DoWork2");
}
public async Task DoWork1()
{
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork1");
await DoNothing();
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork1");
}
public async Task DoWork2()
{
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork2");
await DoNothing();
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork2");
}
public Task DoNothing() { return new Task(() => { /* do nothing */ }); }
}
The program control code:
static void Main(string[] args)
{
var x = new AsyncTests();
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - Main ... calling Start()");
Task.Run(() => x.Start());
Console.WriteLine($"Thread:{Thread.CurrentThread.ManagedThreadId} - Main ... start is running");
Console.ReadKey();
}
The output:
Thread:1 - Main ... calling Start()
Thread:1 - Main ... start is running
Thread:4 - starting whole process, calling await DoWork1()
Thread:4 - starting DoWork1
Thread:4 - starting DoWork2
UPDATE
To make this a little clearer, let's change it so that DoNothing actually calls Thread.Sleep(2000) and my objective is to run two thread sleeps simultaneously and want to use the async/await pattern to achieve this.
If I change "DoNothing" to be an async Task which performs a sleep, then I get told I need await operators in there. Which means I'd need to write yet another async method to be awaited. So what is the best way to end that chain of calls in terms of operators?
Can somebody show a best practise example of how to achieve the above?
You never start your task in DoNothing.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=netframework-4.8#remarks
Separating task creation and execution
The Task class also provides constructors that initialize the task but that do not schedule it for execution. For performance reasons, the Task.Run or TaskFactory.StartNew method is the preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation and scheduling must be separated, you can use the constructors and then call the Task.Start method to schedule the task for execution at a later time.
Instead of creating a task to return, let the language do it for you.
public async Task DoNothing() { }
The above actually does nothing and will return a Task that is in its completed state and can be awaited.
The way you are currently doing it, the Task is created but it is never started or set to Completed, so awaiting it will lock up the program forever.
Just to supplement the answers and comments already given, I wanted to show a code example working the way I intended in the test. It shows the execution flow + for info, the managed thread Id of execution at specific times.
The main code:
class AsyncTests
{
public async Task StartAsync()
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - starting whole process, calling await DoWork1Async()");
await Task.WhenAll(DoWork1Async(), DoWork2Async());
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - finished awaiting DoWork1Async and DoWork2Async");
}
public async Task DoWork1Async()
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork1Async");
await Sleep();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork1Async");
}
public async Task DoWork2Async()
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - starting DoWork2Async");
await Sleep();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - Thread:{Thread.CurrentThread.ManagedThreadId} - finished DoWork2Async");
}
public async Task Sleep()
{
await Task.Delay(2000);
}
}
The calling code (note that to make this run asynchronously I had to leave out the await operator which raises the warning consider applying the 'await' operator on the StartAsync() method call.
static void Main(string[] args)
{
var x = new AsyncTests();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - {Thread.CurrentThread.ManagedThreadId} - Main ... calling Start()");
x.StartAsync();
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} - {Thread.CurrentThread.ManagedThreadId} - Main ... start is running");
Console.ReadKey();
}
Finally, the output - which is as expected showing the code execution/control returning to the places expected for a truly asynchronous operation. As expected two different pool threads were used to run the sleep.
10:43:36.515 - 1 - Main ... calling Start()
10:43:36.546 - Thread:1 - starting whole process, calling await DoWork1Async()
10:43:36.547 - Thread:1 - starting DoWork1Async
10:43:36.561 - Thread:1 - starting DoWork2Async
10:43:36.562 - 1 - Main ... start is running
10:43:38.581 - Thread:4 - finished DoWork2Async
10:43:38.582 - Thread:5 - finished DoWork1Async
10:43:38.582 - Thread:5 - finished awaiting DoWork1Async and DoWork2Async
Let's assume I have these methods:
public async Task<Something> GetSomethingAsync()
{
var somethingService = new SomethingService();
return await service.GetAsync();
}
and
public Task<Something> GetSomethingAsync()
{
var somethingService = new SomethingService();
return service.GetAsync();
}
Both options compile and work the same way. Is there any best practise as to which option is better of if one is faster then the other?
Or is it just some syntactic sugar?
In the first method compiler will generate "state machine" code around it and execution will be returned to the line return await service.GetAsync(); after task will be completed. Consider example below:
public async Task<Something> GetSomethingAsync()
{
var somethingService = new SomethingService();
// Here execution returns to the caller and returned back only when Task is completed.
Something value = await service.GetAsync();
DoSomething();
return value;
}
The line DoSomething(); will be executed only after service.GetAsync task is completed.
Second approach simply starts execution of service.GetAsync and return correspondent Task to the caller without waiting for completion.
public Task<Something> GetSomethingAsync()
{
var somethingService = new SomethingService();
Task<Something> valueTask = service.GetAsync();
DoSomething();
return valueTask;
}
So in the example above DoSomething() will be executed straight after line Task<Something> valueTask = service.GetAsync(); without waiting for completion of task.
Executing async method on the another thread depend on the method itself.
If method execute IO operation, then another thread will be only waste of the thread, which do nothing, only waiting for response. On my opinion async - await are perfect approach for IO operations.
If method GetAsync contains for example Task.Run then execution goes to the another thread fetched from thread pool.
Below is short example, not a good one, but it show the logic a tried to explain:
static async Task GetAsync()
{
for(int i = 0; i < 10; i++)
{
Console.WriteLine($"Iterate GetAsync: {i}");
await Task.Delay(500);
}
}
static Task GetSomethingAsync() => GetAsync();
static void Main(string[] args)
{
Task gettingSomethingTask = GetSomethingAsync();
Console.WriteLine("GetAsync Task returned");
Console.WriteLine("Start sleeping");
Thread.Sleep(3000);
Console.WriteLine("End sleeping");
Console.WriteLine("Before Task awaiting");
gettingSomethingTask.Wait();
Console.WriteLine("After Task awaited");
Console.ReadLine();
}
And output will be next:
Iterate GetAsync: 0
GetAsync Task returned
Start sleeping
Iterate GetAsync: 1
Iterate GetAsync: 2
Iterate GetAsync: 3
Iterate GetAsync: 4
Iterate GetAsync: 5
End sleeping
Before Task awaiting
Iterate GetAsync: 6
Iterate GetAsync: 7
Iterate GetAsync: 8
Iterate GetAsync: 9
After Task awaited
As you can see executing of GetAsync starts straight after calling it.
If GetSomethingAsync() will be changed to the:
static Task GetSomethingAsync() => new Task(async () => await GetAsync());
Where GetAsync wrapped inside another Task, then GetAsync() will not be executed at all and output will be:
GetAsync Task returned
Start sleeping
End sleeping
Before Task awaiting
After Task awaited
Of course you will need to remove line gettingSomethingTask.Wait();, because then application just wait for task which not even started.
i saw some post regarding Async and Await usage in this site. few people are saying that Async and Await complete its job on separate background thread means spawn a new background thread and few people are saying no means Async and Await does not start any separate background thread to complete its job.
so anyone just tell me what happen in case of Async and Await when it is used.
here is one small program
class Program
{
static void Main(string[] args)
{
TestAsyncAwaitMethods();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
public async static void TestAsyncAwaitMethods()
{
await LongRunningMethod();
}
public static async Task<int> LongRunningMethod()
{
Console.WriteLine("Starting Long Running method...");
await Task.Delay(5000);
Console.WriteLine("End Long Running method...");
return 1;
}
}
And the output is:
Starting Long Running method...
Press any key to exit...
End Long Running method...
The problem is that async/await is about asynchrony, not threads.
If you use Task.Run, it will indeed use a background thread (via the Thread Pool, via the Task Parallel Library).
However, for IO operations it relies on IO Completion ports to notify when the operation is complete.
The only guarantee async/await makes is that when an operation completes, it will return to your caller in the SynchronizationContext that was there when it began. In practical terms, that means it will return on the UI Thread (in a Windows application) or to a thread that can return the HTTP Response (in ASP.NET)
A simple way to understand what's going on under the hood, is to use SharpLab, if you paste your short example, you'll get how the C# compiler is rewriting your code containing async / await:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
internal class Program
{
[CompilerGenerated]
private sealed class <TestAsyncAwaitMethods>d__1 : IAsyncStateMachine
{
public int <>1__state;
public AsyncVoidMethodBuilder <>t__builder;
private TaskAwaiter<int> <>u__1;
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter<int> awaiter;
if (num != 0)
{
awaiter = LongRunningMethod().GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<TestAsyncAwaitMethods>d__1 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
[CompilerGenerated]
private sealed class <LongRunningMethod>d__2 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<int> <>t__builder;
private TaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
int result;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
Console.WriteLine("Starting Long Running method...");
awaiter = Task.Delay(5000).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<LongRunningMethod>d__2 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
Console.WriteLine("End Long Running method...");
result = 1;
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult(result);
}
void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}
private static void Main(string[] args)
{
TestAsyncAwaitMethods();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
[AsyncStateMachine(typeof(<TestAsyncAwaitMethods>d__1))]
[DebuggerStepThrough]
public static void TestAsyncAwaitMethods()
{
<TestAsyncAwaitMethods>d__1 stateMachine = new <TestAsyncAwaitMethods>d__1();
stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
stateMachine.<>1__state = -1;
AsyncVoidMethodBuilder <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
}
[AsyncStateMachine(typeof(<LongRunningMethod>d__2))]
[DebuggerStepThrough]
public static Task<int> LongRunningMethod()
{
<LongRunningMethod>d__2 stateMachine = new <LongRunningMethod>d__2();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
AsyncTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
}
As pointed in many other answers on SO (like that one), the async / await rewrite the code as a state machine just like for the yield statement with a method returning either IEnumerator, IEnumerable, IEnumerator<T>, IEnumerable<T>. Except that for async methods, you can return either:
Task<TResult>, for an async method that returns a value.
Task, for an async method that performs an operation but returns no value.
void, for an event handler.
Starting with C# 7.0, any type that has an accessible GetAwaiter method. The object returned by the GetAwaiter method must implement the System.Runtime.CompilerServices.ICriticalNotifyCompletion interface.
About the last bullet you can read more about it (the fact that it's pattern based) here and there. This also involves other subtle choices that are out of the scope of your question but you can have a short explanation here about ValueTask<TResult>, IValueTaskSource<TResult>, etc.
The act of rewriting of the code is delegated to the compiler, Roslyn is basically using the AsyncRewriter class to know how to rewrite the different execution paths, branching to have an equivalent code.
In both cases where you have a valid code containing either yield or async keywords you have an initial state and depending on branching, execution path, the MoveNext() call that occurs behind the scenes will move from one state to another.
Knowing that in the case of a valid async code this kind of snippet below:
case -1:
HelperMethods.Before();
this.awaiter = AsyncMethods.MethodAsync(this.Arg0, this.Arg1).GetAwaiter();
if (!this.awaiter.IsCompleted)
{
this.State = 0;
this.Builder.AwaitUnsafeOnCompleted(ref this.awaiter, ref this);
}
break;
can roughly be translated into (see Dixin's blog for more details):
case -1: // -1 is begin.
HelperMethods.Before(); // Code before 1st await.
this.currentTaskToAwait = AsyncMethods.MethodAsync(this.Arg0, this.Arg1); // 1st task to await
// When this.currentTaskToAwait is done, run this.MoveNext() and go to case 0.
this.State = 0;
this.currentTaskToAwait.ContinueWith(_ => that.MoveNext()); // Callback
break;
Bear that in mind that if you have void as a return type of an async method you won't have much currentTaskToAwait =]
few people are saying that Async and Await complete its job on separate background thread means spawn a new background thread and few people are saying no means Async and Await does not start any separate background thread to complete its job.
Regarding your code, you can track which thread is (ie. id) used and whether it is from a pool or not:
public static class Program
{
private static void DisplayCurrentThread(string prefix)
{
Console.WriteLine($"{prefix} - Thread Id: {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"{prefix} - ThreadPool: {Thread.CurrentThread.IsThreadPoolThread}");
}
public static void Main(params string[] args)
{
DisplayCurrentThread("Main Pre");
TestAsyncAwaitMethods();
DisplayCurrentThread("Main Post");
Console.ReadLine();
}
private static async void TestAsyncAwaitMethods()
{
DisplayCurrentThread("TestAsyncAwaitMethods Pre");
await LongRunningMethod();
DisplayCurrentThread("TestAsyncAwaitMethods Post");
}
private static async Task<int> LongRunningMethod()
{
DisplayCurrentThread("LongRunningMethod Pre");
Console.WriteLine("Starting Long Running method...");
await Task.Delay(500);
Console.WriteLine("End Long Running method...");
DisplayCurrentThread("LongRunningMethod Post");
return 1;
}
}
Will output for example:
Main Pre - Thread Id: 1
Main Pre - ThreadPool: False
TestAsyncAwaitMethods Pre - Thread Id: 1
TestAsyncAwaitMethods Pre - ThreadPool: False
LongRunningMethod Pre - Thread Id: 1
LongRunningMethod Pre - ThreadPool: False
Starting Long Running method...
Main Post - Thread Id: 1
Main Post - ThreadPool: False
End Long Running method...
LongRunningMethod Post - Thread Id: 4
LongRunningMethod Post - ThreadPool: True
TestAsyncAwaitMethods Post - Thread Id: 4
TestAsyncAwaitMethods Post - ThreadPool: True
You can notice that that the LongRunningMethod method terminates after the Main method, it's due to the fact that you used void as a return type for asynchronous method. An async void method should only be used for event handlers and nothing else (see Async/Await - Best Practices in Asynchronous Programming)
Also, as already mentionned by i3arnon, since no context has been passed, yes the program does (re)use a thread from the thread pool to resume its execution after the async method call.
About those "contexts", I would suggest you to read that article, the article will clarify what is a context, in particular a SynchronizationContext.
Beware that I said a threadpool thread to "resume" and not to execute the async piece of code, you can find out more about this here.
async methods are usually designed to leverage whathever latency is inherent to the underlying call, usually IO, eg. writing, reading something on a disk, querying something over the network and so forth.
The purpose of truly async methods is to avoid using threads for IO stuff which can help application to scale when you have a lot more requests. Typically can handle more requests in ASP.NET WebAPI with async resources since each of them will "free" the thread of the request whenever they will hit the database or whathever async-able calls you are making in that resource.
I suggest you to read the answers of that question
Void-returning async methods have a specific purpose: to make asynchronous event handlers possible. It is possible to have an event handler that returns some actual type, but that doesn't work well with the language; invoking an event handler that returns a type is very awkward, and the notion of an event handler actually returning something doesn't make much sense.
Event handlers naturally return void, so async methods return void so that you can have an asynchronous event handler. However, some semantics of an async void method are subtly different than the semantics of an async Task or async Task method.
A way to avoid this is to leverage a C# 7.1 feature and expect a Task as a return type instead of the void:
public static class Program
{
private static void DisplayCurrentThread(string prefix)
{
Console.WriteLine($"{prefix} - Thread Id: {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"{prefix} - ThreadPool: {Thread.CurrentThread.IsThreadPoolThread}");
}
public static async Task Main(params string[] args)
{
DisplayCurrentThread("Main Pre");
await TestAsyncAwaitMethods();
DisplayCurrentThread("Main Post");
Console.ReadLine();
}
private static async Task TestAsyncAwaitMethods()
{
DisplayCurrentThread("TestAsyncAwaitMethods Pre");
await LongRunningMethod();
DisplayCurrentThread("TestAsyncAwaitMethods Post");
}
private static async Task<int> LongRunningMethod()
{
DisplayCurrentThread("LongRunningMethod Pre");
Console.WriteLine("Starting Long Running method...");
await Task.Delay(500);
Console.WriteLine("End Long Running method...");
DisplayCurrentThread("LongRunningMethod Post");
return 1;
}
}
You'll then get
Main Pre - Thread Id: 1
Main Pre - ThreadPool: False
TestAsyncAwaitMethods Pre - Thread Id: 1
TestAsyncAwaitMethods Pre - ThreadPool: False
LongRunningMethod Pre - Thread Id: 1
LongRunningMethod Pre - ThreadPool: False
Starting Long Running method...
End Long Running method...
LongRunningMethod Post - Thread Id: 4
LongRunningMethod Post - ThreadPool: True
TestAsyncAwaitMethods Post - Thread Id: 4
TestAsyncAwaitMethods Post - ThreadPool: True
Main Post - Thread Id: 4
Main Post - ThreadPool: True
Which looks more like what you would normally expect.
More resources about async / await:
Dixin's Blog: Understanding C# async / await (1) Compilation
Dixin's Blog: Understanding C# async / await (2) Awaitable-Awaiter Pattern
Dixin's Blog: Understanding C# async / await (3) Runtime Context
Stephen Cleary: async and await
Stephen Cleary: There is no thread
Stephen Toub: ExecutionContext vs SynchronizationContext
Both of the your statements are probably true, but are confusing.
Async-await does usually complete on a separate background thread but it doesn't mean it starts any separate background thread to complete the job.
The point of these asynchronous operations is to to not hold a thread while an asynchronous operation is being executed because true asynchronous operations do not require a thread.
The parts before that operation can be CPU bound and do require a thread and they are executed by the calling thread. The parts after that operation (which is usually called the completion) also require a thread. If there's a SynchronizationContext (like there is in UI or asp.net apps) or TaskScheduler then that part is handled by them. If there isn't any that part is scheduled on the ThreadPool to be executed by an already existing background thread.
So, in your example Task.Delay creates a Task that completes after 5 seconds. During that delay there's no need for a thread so you can use async-await.
The flow of your example is this: The main thread starts executing Main, calls TestAsyncAwaitMethods, calls LongRunningMethod, prints the first message, calls Task.Delay, registers the rest of the method as a continuation to execute after the Task.Delay completes, return to Main, print the message and waits synchronously (blocks) on Console.ReadLine.
After 5 seconds the timer in Task.Delay ends and completes the Task returned from Task.Delay. The continuation is then scheduled on the ThreadPool (since it's a console app) and a ThreadPool thread that was assigned that task prints "End Long Running method...".
In conclusion, a true asynchronous operation doesn't need a thread to be able to run, but it does need a thread after it has completed which is usually a background thread from the ThreadPool but not necessarily.
You are asking the wrong question
In effect you are asking, how does a parcel get to my doorstep? By ship or by plane?
The point is that your door step doesn't care wither the parcel was delivered by sea or air.
However the main reason for Microsoft to develop the Task/async/await framework was to take advantage of Event based programming as opposed to Thread based programming.
In general Event based programming is MUCH more efficient and faster than Thread based programming. Which is why most of the .net API uses it. Up until now, however most people avoided Event based programming because it is extremely difficult to understand (again, async/wait was put into place to make this simple).
Calling await is only possible inside methods marked as async. Once you await a function, the framework knows how to remember your current calling environment and return control to it once the awaited function completes.
You can only ever await functions that return Tasks. So all await deals with is the Task object that gets returned (and until a task is returned, the method you are awaiting is executing synchronously)
To provide you with a Task, the method you are awaiting could spawn a new thread to do it's job, it could synchronously return a completed task with a value (creating a task from a result), it can do whatever it wants. All await does is give the control back to the parent of your function until and unless the Task object you received from the awaitable method is complete. At that point it will continue the execution of your method from the await line.
Need understand two things: a) async/await use tasks(tasks use thread pool) b) async/await is NOT for parallel work.
Just compile this and look at Id's:
static void Main(string[] args)
{
Console.WriteLine("Id main thread is: {0}", Thread.CurrentThread.ManagedThreadId);
TestAsyncAwaitMethods();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
public async static void TestAsyncAwaitMethods()
{
Console.WriteLine("Id thread (void - 0) is: {0}", Thread.CurrentThread.ManagedThreadId);
var _value = await LongRunningMethod();
Console.WriteLine("Id thread (void - 1) is: {0}", Thread.CurrentThread.ManagedThreadId);
}
public static async Task<int> LongRunningMethod()
{
Console.WriteLine("Id thread (int) is: {0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Starting Long Running method...");
await Task.Delay(1000);
Console.WriteLine("End Long Running method...");
return 1;
}
The simplest solution is,
await LongRunningMethod().wait();
It will cause the main thread to wait (non blocking) till the LongRunningMethod finishes execution.
Given the following code...
static void DoSomething(int id) {
Thread.Sleep(50);
Console.WriteLine(#"DidSomething({0})", id);
}
I know I can convert this to an async task as follows...
static async Task DoSomethingAsync(int id) {
await Task.Delay(50);
Console.WriteLine(#"DidSomethingAsync({0})", id);
}
And that by doing so if I am calling multiple times (Task.WhenAll) everything will be faster and more efficient than perhaps using Parallel.Foreach or even calling from within a loop.
But for a minute, lets pretend that Task.Delay() does not exist and I actually have to use Thread.Sleep(); I know in reality this is not the case, but this is concept code and where the Delay/Sleep is would normally be an IO operation where there is no async option (such as early EF).
I have tried the following...
static async Task DoSomethingAsync2(int id) {
await Task.Run(() => {
Thread.Sleep(50);
Console.WriteLine(#"DidSomethingAsync({0})", id);
});
}
But, though it runs without error, according to Lucien Wischik this is in fact bad practice as it is merely spinning up threads from the pool to complete each task (it is also slower using the following console application - if you swap between DoSomethingAsync and DoSomethingAsync2 call you can see a significant difference in the time that it takes to complete)...
static void Main(string[] args) {
MainAsync(args).Wait();
}
static async Task MainAsync(String[] args) {
List<Task> tasks = new List<Task>();
for (int i = 1; i <= 1000; i++)
tasks.Add(DoSomethingAsync2(i)); // Can replace with any version
await Task.WhenAll(tasks);
}
I then tried the following...
static async Task DoSomethingAsync3(int id) {
await new Task(() => {
Thread.Sleep(50);
Console.WriteLine(#"DidSomethingAsync({0})", id);
});
}
Transplanting this in place of the original DoSomethingAsync, the test never completes and nothing is shown on screen!
I have also tried multiple other variations that either do not compile or do not complete!
So, given the constraint that you cannot call any existing asynchronous methods and must complete both the Thread.Sleep and the Console.WriteLine in an asynchronous task, how do you do it in a manner that is as efficient as the original code?
The objective here for those of you who are interested is to give me a better understanding of how to create my own async methods where I am not calling anybody elses. Despite many searches, this seems to be the one area where examples are really lacking - whilst there are many thousands of examples of calling async methods that call other async methods in turn I cannot find any that convert an existing void method to an async task where there is no call to a further async task other than those that use the Task.Run(() => {} ) method.
There are two kinds of tasks: those that execute code (e.g., Task.Run and friends), and those that respond to some external event (e.g., TaskCompletionSource<T> and friends).
What you're looking for is TaskCompletionSource<T>. There are various "shorthand" forms for common situations so you don't always have to use TaskCompletionSource<T> directly. For example, Task.FromResult or TaskFactory.FromAsync. FromAsync is most commonly used if you have an existing *Begin/*End implementation of your I/O; otherwise, you can use TaskCompletionSource<T> directly.
For more information, see the "I/O-bound Tasks" section of Implementing the Task-based Asynchronous Pattern.
The Task constructor is (unfortunately) a holdover from Task-based parallelism, and should not be used in asynchronous code. It can only be used to create a code-based task, not an external event task.
So, given the constraint that you cannot call any existing asynchronous methods and must complete both the Thread.Sleep and the Console.WriteLine in an asynchronous task, how do you do it in a manner that is as efficient as the original code?
I would use a timer of some kind and have it complete a TaskCompletionSource<T> when the timer fires. I'm almost positive that's what the actual Task.Delay implementation does anyway.
So, given the constraint that you cannot call any existing
asynchronous methods and must complete both the Thread.Sleep and the
Console.WriteLine in an asynchronous task, how do you do it in a
manner that is as efficient as the original code?
IMO, this is a very synthetic constraint that you really need to stick with Thread.Sleep. Under this constraint, you still can slightly improve your Thread.Sleep-based code. Instead of this:
static async Task DoSomethingAsync2(int id) {
await Task.Run(() => {
Thread.Sleep(50);
Console.WriteLine(#"DidSomethingAsync({0})", id);
});
}
You could do this:
static Task DoSomethingAsync2(int id) {
return Task.Run(() => {
Thread.Sleep(50);
Console.WriteLine(#"DidSomethingAsync({0})", id);
});
}
This way, you'd avoid an overhead of the compiler-generated state machine class. There is a subtle difference between these two code fragments, in how exceptions are propagated.
Anyhow, this is not where the bottleneck of the slowdown is.
(it is also slower using the following console application - if you
swap between DoSomethingAsync and DoSomethingAsync2 call you can see a
significant difference in the time that it takes to complete)
Let's look one more time at your main loop code:
static async Task MainAsync(String[] args) {
List<Task> tasks = new List<Task>();
for (int i = 1; i <= 1000; i++)
tasks.Add(DoSomethingAsync2(i)); // Can replace with any version
await Task.WhenAll(tasks);
}
Technically, it requests 1000 tasks to be run in parallel, each supposedly to run on its own thread. In an ideal universe, you'd expect to execute Thread.Sleep(50) 1000 times in parallel and complete the whole thing in about 50ms.
However, this request is never satisfied by the TPL's default task scheduler, for a good reason: thread is a precious and expensive resource. Moreover, the actual number of concurrent operations is limited to the number of CPUs/cores. So in reality, with the default size of ThreadPool, I'm getting 21 pool threads (at peak) serving this operation in parallel. That is why DoSomethingAsync2 / Thread.Sleep takes so much longer than DoSomethingAsync / Task.Delay. DoSomethingAsync doesn't block a pool thread, it only requests one upon the completion of the time-out. Thus, more DoSomethingAsync tasks can actually run in parallel, than DoSomethingAsync2 those.
The test (a console app):
// https://stackoverflow.com/q/21800450/1768303
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Console_21800450
{
public class Program
{
static async Task DoSomethingAsync(int id)
{
await Task.Delay(50);
UpdateMaxThreads();
Console.WriteLine(#"DidSomethingAsync({0})", id);
}
static async Task DoSomethingAsync2(int id)
{
await Task.Run(() =>
{
Thread.Sleep(50);
UpdateMaxThreads();
Console.WriteLine(#"DidSomethingAsync2({0})", id);
});
}
static async Task MainAsync(Func<int, Task> tester)
{
List<Task> tasks = new List<Task>();
for (int i = 1; i <= 1000; i++)
tasks.Add(tester(i)); // Can replace with any version
await Task.WhenAll(tasks);
}
volatile static int s_maxThreads = 0;
static void UpdateMaxThreads()
{
var threads = Process.GetCurrentProcess().Threads.Count;
// not using locks for simplicity
if (s_maxThreads < threads)
s_maxThreads = threads;
}
static void TestAsync(Func<int, Task> tester)
{
s_maxThreads = 0;
var stopwatch = new Stopwatch();
stopwatch.Start();
MainAsync(tester).Wait();
Console.WriteLine(
"time, ms: " + stopwatch.ElapsedMilliseconds +
", threads at peak: " + s_maxThreads);
}
static void Main()
{
Console.WriteLine("Press enter to test with Task.Delay ...");
Console.ReadLine();
TestAsync(DoSomethingAsync);
Console.ReadLine();
Console.WriteLine("Press enter to test with Thread.Sleep ...");
Console.ReadLine();
TestAsync(DoSomethingAsync2);
Console.ReadLine();
}
}
}
Output:
Press enter to test with Task.Delay ...
...
time, ms: 1077, threads at peak: 13
Press enter to test with Thread.Sleep ...
...
time, ms: 8684, threads at peak: 21
Is it possible to improve the timing figure for the Thread.Sleep-based DoSomethingAsync2? The only way I can think of is to use TaskCreationOptions.LongRunning with Task.Factory.StartNew:
You should think twice before doing this in any real-life application:
static async Task DoSomethingAsync2(int id)
{
await Task.Factory.StartNew(() =>
{
Thread.Sleep(50);
UpdateMaxThreads();
Console.WriteLine(#"DidSomethingAsync2({0})", id);
}, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
}
// ...
static void Main()
{
Console.WriteLine("Press enter to test with Task.Delay ...");
Console.ReadLine();
TestAsync(DoSomethingAsync);
Console.ReadLine();
Console.WriteLine("Press enter to test with Thread.Sleep ...");
Console.ReadLine();
TestAsync(DoSomethingAsync2);
Console.ReadLine();
}
Output:
Press enter to test with Thread.Sleep ...
...
time, ms: 3600, threads at peak: 163
The timing gets better, but the price for this is high. This code asks the task scheduler to create a new thread for each new task. Do not expect this thread to come from the pool:
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Thread pool: " +
Thread.CurrentThread.IsThreadPoolThread); // false!
}, TaskCreationOptions.LongRunning).Wait();