1. Introduction
Let's first look at a small example below: a Winfrom program, there is a button on the interface, and there are two asynchronous methods. Click the button to call two asynchronous methods, and the execution order pops up, and the code is as follows:
using System; using ; using ; using ; namespace TPLDemoSln { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// Button click event /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStart_Click(object sender, EventArgs e) { string i1 = await F1Async(); ("i1=" + i1); string i2 = await F2Async(); ("i2=" + i2); } /// <summary> /// Asynchronous method F1 /// </summary> /// <returns></returns> private Task<string> F1Async() { ("F1 Start"); return <string>(() => { // Sleep for 1 second (1000); ("F1 Run"); return "F1"; }); } /// <summary> /// Asynchronous method F2 /// </summary> /// <returns></returns> private Task<string> F2Async() { ("F2 Start"); return <string>(() => { // Sleep for 2 seconds (2000); ("F2 Run"); return "F2"; }); } } }
In the above code, () is a method used to wrap a code segment into a Task<T>. The code body delegated in Run is the logic of asynchronous task execution, and finally return the value.
Running the program can get the following output sequence:
F1 Start->F1 Run->i1=F1->F2 Start->F2 Run->i2=F2。
We modify the button event and modify it to the following code to see the execution order:
/// <summary> /// Button click event/// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStart_Click(object sender, EventArgs e) { //string i1 = await F1Async(); //("i1=" + i1); //string i2 = await F2Async(); //("i2=" + i2); Task<string> task1 = F1Async(); Task<string> task2 = F2Async(); string i1 = await task1; ("i1=" + i1); string i2 = await task2; ("i2=" + i2); }
Run the program again to view the output order:
F1 Start->F2 Start->F1 Run->i1=F1->F2 Run->i2=F2。
It can be seen that the execution order of the two times is inconsistent. What is the reason for this?
This is because it is not until await that the Task asynchronous task begins to be executed. When the Task<string> task1=F1Async() code is executed, the F1Async asynchronous task begins to be executed. Similarly, after executing the next code, the F2Async asynchronous task begins to be executed. await is to ensure that the asynchronous task must be executed when it is executed here. When executing await, if the asynchronous task has not been executed yet, then wait for the asynchronous task to be executed. If the asynchronous task has been executed, then the return value of the asynchronous task is directly obtained.
Is the reason we explained above correct? We can do the following experiment to verify the reason mentioned above. We comment out the await in the button event to see if the asynchronous method will be executed. The code is as follows:
/// <summary> /// Button click event/// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStart_Click(object sender, EventArgs e) { //string i1 = await F1Async(); //("i1=" + i1); //string i2 = await F2Async(); //("i2=" + i2); Task<string> task1 = F1Async(); Task<string> task2 = F2Async(); // It is not until await that the task asynchronous task starts to be executed. Here we ensure that the asynchronous task must be executed. // When the code is executed here, if it is not finished, wait for the execution. If it has been completed, the return value will be obtained directly. // Comment out the await code snippet here to see if the asynchronous method will be executed //string i1 = await task1; //("i1=" + i1); //string i2 = await task2; //("i2=" + i2); }
When you run the program and find that the asynchronous method will still be executed, this means that the reason we explained above is correct. Those who are interested can use Reflector to decompile to view the principles of internal implementation, mainly within the MoveNext() method.
We can get the following conclusion:
- As long as the method is a return value of type Task<T>, you can use await to wait for the call to get the return value.
- If a method that returns the Task<T> type is marked async, then just return an instance of this type T directly inside the method.
- If a method that returns the Task<T> type is not marked as async, then it is necessary to directly return a Task instance inside the method.
The second point mentioned above is the code below
/// <summary> /// The method is marked as async and directly returns an int-type value./// </summary> /// <returns></returns> private async Task<int> F3Async() { return 2; }
For the third point above, you can see the following code:
/// <summary> /// The method is not marked as async, and it returns a Task directly/// </summary> /// <returns></returns> private Task<int> F4Async() { return <int>(() => { return 2; }); }
2. TPL Advanced
Let's make some summary:
1. If there is await inside the method, the method must be marked as async. await and async appear in pairs, and only await and no async program will report an error. Only async does not have await, the program will be executed according to the synchronous method.
2. The Action method in MVC and the event handling method in WinForm can both be marked as async, and the Main() method of the console cannot be marked as async. What should I do with methods that cannot be marked as async? We can use the Result property to get the value, see the following code:
using System; using ; using ; namespace AsyncDemo { class Program { static void Main(string[] args) { // Instantiate the object HttpClient client = new HttpClient(); // Call the asynchronous Get method Task<HttpResponseMessage> taskMsg = (""); // Get the return value through the Result property HttpResponseMessage msg = ; Task<string> taskRead = (); string html = ; (html); (); } } }
This method is not recommended, as this does not reflect the benefits of asynchronousness, and using the Result property may cause deadlocks caused by context switching.
Let’s take a look at the method of creating a Task.
1. If the return value is a value that can be obtained immediately, then use (). Look at the following code:
static Task<int> TestAsync() { //return <int>(() => //{ // return 5; //}); // Simple writing method return (3); }
2. If it is a task that requires a rest (for example, if the download fails, try again after 5 seconds. The main thread does not rest, which is different), then use ().
3. () will convert IAsyncResult to Task, so that the APM-style API can also be called with await.
4. Simplified writing of asynchronous methods. If the method is declared as async, you can directly return the specific value without creating a task. The compiler creates the task. See the following code:
static async Task<int> Test2Async() { // Complex writing //return await <int>(() => //{ // return 5; //}); // The following is a simplified writing method, return directly return 6; }
This is all about this article about the exploration of advanced usage of C# multithreaded TPL mode. I hope it will be helpful to everyone's learning and I hope everyone will support me more.