A preliminary exploration of asynchronous programming: async and await C# basic series - delegation and design patterns (2)

 

 

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:

 

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.

copy code
     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";
        }
copy code

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:

copy code
     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";
        }
copy code

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:

copy code
            // 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();
copy code

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.

copy code
        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";
            });
        }
copy code

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.  

copy code
     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"));
        }
copy code

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

copy code
public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion
    {
        public bool IsCompleted { get; }
        public TResult GetResult();
        public void OnCompleted(Action continuation);
        public void UnsafeOnCompleted(Action continuation);
   }    
copy code

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:

 

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.

copy code
     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";
        }
copy code

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:

copy code
     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";
        }
copy code

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:

copy code
            // 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();
copy code

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.

copy code
        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";
            });
        }
copy code

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.  

copy code
     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"));
        }
copy code

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

copy code
public struct TaskAwaiter<TResult> : ICriticalNotifyCompletion, INotifyCompletion
    {
        public bool IsCompleted { get; }
        public TResult GetResult();
        public void OnCompleted(Action continuation);
        public void UnsafeOnCompleted(Action continuation);
   }    
copy code

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.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325251851&siteId=291194637