Foreword: The previous article introduced several usages of multithreading from the application level. Some bloggers talked about new syntaxes such as async and await. Indeed, multithreading without asynchronous is monotonous and tedious. Async and await appeared after C# 5.0, and its appearance brought great convenience to asynchronous parallelism. There are still many things involved in asynchronous programming. This article will first introduce the principles and simple implementation of async and await.
C# basic series directory:
- C# basic series - Linq to Xml read and write xml
- C# basic series - the use of extension methods
- C# Basic Series - Serialization Efficiency Competition
- C# Basic Series - Reflection Notes
- C# basic series - Attribute attribute use
- C# Basic Series - Small Generics
- C# basic series - detailed explanation of common usage of multithreading
- C# Basic Series - Delegation and Design Patterns (1)
- C# Basic Series - Delegation and Design Patterns (2)
- C# Fundamentals Series - Never Worry About Interviewers Asking Me About "Events" Again
The previous C# basic series - a detailed explanation of the common usage of multi-threading mentioned that the multi-threaded new Thread() method has no solution for a delegate with a return value type. If a return value is required, it must rely on the asynchronous method. . Before understanding asynchrony, let's take a look at the Task object, an upgraded version of the Thread object:
1. The past and present of the Task object: The Task object is an important object of asynchronous programming that appeared after .Net Framework 4.0. To a certain extent, the Task object can understand an upgraded product of the Thread object. Since it is an upgraded product, it must have its advantages, such as the problem that our Thread object cannot solve above: for delegates with return value types. Task objects can be easily solved.
static void Main(string[] args) { Console.WriteLine("Time before executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); });//Start the Task execution method Console.WriteLine("Time after executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine(strRes.Result);//Get the return value of the method Console.WriteLine("The time after getting the result: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Thread.Sleep(2000); return "I am the return value"; }
First look at the results:
From the result analysis, it can be seen that after executing the sentence var strRes = Task.Run<string>(() => { return GetReturnResult(); }), the main thread does not block to execute the GetReturnResult() method, but starts another The thread executes the GetReturnResult() method. The main thread will not wait for the GetReturnResult() method to complete until the sentence strRes.Result is executed. Why it is said that another thread is opened, we can see it more clearly through the thread ID:
static void Main(string[] args) { Console.WriteLine("Time before executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); }); Console.WriteLine("Time after executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("I am the main thread, thread ID: " + Thread.CurrentThread.ManagedThreadId); Console.WriteLine(strRes.Result); Console.WriteLine("The time after getting the result: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Console.WriteLine("I am the thread in GetReturnResult, thread ID: " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); return "I am the return value"; }
result:
From this, we can see that Task.Run<string>(()=>{}).Reslut blocks the main thread, because the main thread must wait for the method execution to complete in order to get the return value.
The usage of the Task object is as follows:
// usage one Task task1 = new Task(new Action(MyAction)); // Usage 2 Task task2 = new Task(delegate { MyAction(); }); // Usage three Task task3 = new Task(() => MyAction()); Task task4 = new Task(() => { MyAction(); }); task1.Start(); task2.Start(); task3.Start(); task4.Start();
It can be seen from the above that the constructor of the Task object passes in a delegate. Since the delegate of the Action type can be passed in, it is conceivable that the parameters of the 16 types of Action can come in handy again. So there is no need to say more about the transfer of Task object parameters. For details, please refer to the usage of Action delegate in C# Basic Series - Delegates and Design Patterns (1) .
2. First acquaintance with async & await.
static void Main(string[] args) { Console.WriteLine("I am the main thread, thread ID: {0}", Thread.CurrentThread.ManagedThreadId); TestAsync (); Console.ReadLine(); } static async Task TestAsync() { Console.WriteLine("Before calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); var name = GetReturnResult(); Console.WriteLine("After calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); Console.WriteLine("Get the result of GetReturnResult() method: {0}. Current time: {1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); } static async Task<string> GetReturnResult() { Console.WriteLine("Before executing Task.Run, thread ID: {0}", Thread.CurrentThread.ManagedThreadId); return await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("Thread ID in the GetReturnResult() method: {0}", Thread.CurrentThread.ManagedThreadId); return "I am the return value"; }); }
result:
Let's take a look at the execution of the program:
From the above results, the following conclusions can be drawn:
(1) In the method body identified by async, if there is no await keyword, then this method is no different from calling an ordinary method.
(2) In the method body identified by async, before the await keyword appears, the main thread is called sequentially, and thread blocking will not occur until the await keyword appears.
(3) The await keyword can be understood as waiting for the completion of the execution of the method. In addition to the methods marked with the async keyword, the Task object can also be marked, indicating that the thread is waiting for the completion of execution. So the await keyword is not for the async method, but for the Task returned to us by the async method.
(4) Whether the async keyword can only identify the method that returns the Task object. Let's try:
The return type of an asynchronous method must be of type void, Task, or Task<T>. That is to say, async is either void or associated with Task.
3. In addition to the await keyword, the Task object has another way to wait for the execution result.
static async Task TestAsync() { Console.WriteLine("Before calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); var name = GetReturnResult(); Console.WriteLine("After calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); Console.WriteLine("Get the result of GetReturnResult() method: {0}. Current time: {1}", name.GetAwaiter().GetResult(), DateTime.Now.ToString("yyyy-MM-dd hh:MM :ss")); }
This gives the same result.
The name.GetAwaiter() method gets a TaskAwaiter object, which represents the object of the asynchronous task waiting to be completed, and provides the parameters of the result. So in addition to completing the waiting of the await keyword, it can also do some other operations. We turn the TaskAwaiter to the definition
public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion { public bool IsCompleted { get; } public TResult GetResult(); public void OnCompleted(Action continuation); public void UnsafeOnCompleted(Action continuation); }
IsCompleted: Gets a value that indicates whether the asynchronous task has completed.
GetResult(): Get the result of execution. This method has the same effect as the await keyword.
OnCompleted(): Pass in a delegate to execute after the task is completed.
UnsafeOnCompleted(): Schedules a continuation of the asynchronous task associated with this awaiter.
It can be seen that the await keyword actually calls the GetResult() method of the TaskAwaiter object.
Foreword: The previous article introduced several usages of multithreading from the application level. Some bloggers talked about new syntaxes such as async and await. Indeed, multithreading without asynchronous is monotonous and tedious. Async and await appeared after C# 5.0, and its appearance brought great convenience to asynchronous parallelism. There are still many things involved in asynchronous programming. This article will first introduce the principles and simple implementation of async and await.
C# basic series directory:
- C# basic series - Linq to Xml read and write xml
- C# basic series - the use of extension methods
- C# Basic Series - Serialization Efficiency Competition
- C# Basic Series - Reflection Notes
- C# basic series - Attribute attribute use
- C# Basic Series - Small Generics
- C# basic series - detailed explanation of common usage of multithreading
- C# Basic Series - Delegation and Design Patterns (1)
- C# Basic Series - Delegation and Design Patterns (2)
- C# Fundamentals Series - Never Worry About Interviewers Asking Me About "Events" Again
The previous C# basic series - a detailed explanation of the common usage of multi-threading mentioned that the multi-threaded new Thread() method has no solution for a delegate with a return value type. If a return value is required, it must rely on the asynchronous method. . Before understanding asynchrony, let's take a look at the Task object, an upgraded version of the Thread object:
1. The past and present of the Task object: The Task object is an important object of asynchronous programming that appeared after .Net Framework 4.0. To a certain extent, the Task object can understand an upgraded product of the Thread object. Since it is an upgraded product, it must have its advantages, such as the problem that our Thread object cannot solve above: for delegates with return value types. Task objects can be easily solved.
static void Main(string[] args) { Console.WriteLine("Time before executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); });//Start the Task execution method Console.WriteLine("Time after executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine(strRes.Result);//Get the return value of the method Console.WriteLine("The time after getting the result: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Thread.Sleep(2000); return "I am the return value"; }
First look at the results:
From the result analysis, it can be seen that after executing the sentence var strRes = Task.Run<string>(() => { return GetReturnResult(); }), the main thread does not block to execute the GetReturnResult() method, but starts another The thread executes the GetReturnResult() method. The main thread will not wait for the GetReturnResult() method to complete until the sentence strRes.Result is executed. Why it is said that another thread is opened, we can see it more clearly through the thread ID:
static void Main(string[] args) { Console.WriteLine("Time before executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); var strRes = Task.Run<string>(() => { return GetReturnResult(); }); Console.WriteLine("Time after executing GetReturnResult method: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.WriteLine("I am the main thread, thread ID: " + Thread.CurrentThread.ManagedThreadId); Console.WriteLine(strRes.Result); Console.WriteLine("The time after getting the result: " + DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); Console.ReadLine(); } static string GetReturnResult() { Console.WriteLine("I am the thread in GetReturnResult, thread ID: " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(2000); return "I am the return value"; }
result:
From this, we can see that Task.Run<string>(()=>{}).Reslut blocks the main thread, because the main thread must wait for the method execution to complete in order to get the return value.
The usage of the Task object is as follows:
// usage one Task task1 = new Task(new Action(MyAction)); // Usage 2 Task task2 = new Task(delegate { MyAction(); }); // Usage three Task task3 = new Task(() => MyAction()); Task task4 = new Task(() => { MyAction(); }); task1.Start(); task2.Start(); task3.Start(); task4.Start();
It can be seen from the above that the constructor of the Task object passes in a delegate. Since the delegate of the Action type can be passed in, it is conceivable that the parameters of the 16 types of Action can come in handy again. So there is no need to say more about the transfer of Task object parameters. For details, please refer to the usage of Action delegate in C# Basic Series - Delegates and Design Patterns (1) .
2. First acquaintance with async & await.
static void Main(string[] args) { Console.WriteLine("I am the main thread, thread ID: {0}", Thread.CurrentThread.ManagedThreadId); TestAsync (); Console.ReadLine(); } static async Task TestAsync() { Console.WriteLine("Before calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); var name = GetReturnResult(); Console.WriteLine("After calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); Console.WriteLine("Get the result of GetReturnResult() method: {0}. Current time: {1}", await name, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss")); } static async Task<string> GetReturnResult() { Console.WriteLine("Before executing Task.Run, thread ID: {0}", Thread.CurrentThread.ManagedThreadId); return await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine("Thread ID in the GetReturnResult() method: {0}", Thread.CurrentThread.ManagedThreadId); return "I am the return value"; }); }
result:
Let's take a look at the execution of the program:
From the above results, the following conclusions can be drawn:
(1) In the method body identified by async, if there is no await keyword, then this method is no different from calling an ordinary method.
(2) In the method body identified by async, before the await keyword appears, the main thread is called sequentially, and thread blocking will not occur until the await keyword appears.
(3) The await keyword can be understood as waiting for the completion of the execution of the method. In addition to the methods marked with the async keyword, the Task object can also be marked, indicating that the thread is waiting for the completion of execution. So the await keyword is not for the async method, but for the Task returned to us by the async method.
(4) Whether the async keyword can only identify the method that returns the Task object. Let's try:
The return type of an asynchronous method must be of type void, Task, or Task<T>. That is to say, async is either void or associated with Task.
3. In addition to the await keyword, the Task object has another way to wait for the execution result.
static async Task TestAsync() { Console.WriteLine("Before calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); var name = GetReturnResult(); Console.WriteLine("After calling GetReturnResult(), thread ID: {0}. Current time: {1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd hh:MM:ss" )); Console.WriteLine("Get the result of GetReturnResult() method: {0}. Current time: {1}", name.GetAwaiter().GetResult(), DateTime.Now.ToString("yyyy-MM-dd hh:MM :ss")); }
This gives the same result.
The name.GetAwaiter() method gets a TaskAwaiter object, which represents the object of the asynchronous task waiting to be completed, and provides the parameters of the result. So in addition to completing the waiting of the await keyword, it can also do some other operations. We turn the TaskAwaiter to the definition
public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion { public bool IsCompleted { get; } public TResult GetResult(); public void OnCompleted(Action continuation); public void UnsafeOnCompleted(Action continuation); }
IsCompleted: Gets a value that indicates whether the asynchronous task has completed.
GetResult(): Get the result of execution. This method has the same effect as the await keyword.
OnCompleted(): Pass in a delegate to execute after the task is completed.
UnsafeOnCompleted(): Schedules a continuation of the asynchronous task associated with this awaiter.
It can be seen that the await keyword actually calls the GetResult() method of the TaskAwaiter object.