C # multithreading and asynchronous programming -- detailed explanation of Task and async/await

catalogue

1, What is asynchronous

2, Task introduction

1 Task creation and running

2. Blocking method of task (Wait/WaitAll/WaitAny)

3, Asynchronous method (async/await)

1, What is asynchronous

Synchronous and asynchronous are mainly used to modify methods. When a method is called, the caller needs to wait for the method to complete execution and return to continue execution. We call this method a synchronous method; When a method is called, it returns immediately and gets a thread to execute the business inside the method. The caller does not have to wait for the method to complete execution. We call this method asynchronous method.

The advantage of asynchrony is that it is non blocking (the calling thread will not pause to wait for the sub thread to complete). Therefore, we set some time-consuming tasks that do not need to use the results immediately as asynchronous execution, which can improve the running efficiency of the program. net4.0 introduces the Task class based on ThreadPool. Microsoft strongly recommends using Task to execute asynchronous tasks. Now, Task is basically used in asynchronous methods in C# class library; net5.0 introduced async/await to make asynchronous programming more convenient. This chapter mainly introduces the contents related to Task and async/await, and other asynchronous operation methods will be introduced in the next chapter.

 

2, Task introduction

Task is launched on the basis of ThreadPool. Let's briefly understand ThreadPool. There are several threads in the ThreadPool. If a task needs to be processed, an idle thread will be obtained from the thread pool to execute the task. After the task is executed, the thread will not be destroyed, but recycled by the thread pool for subsequent tasks. When all threads in the thread pool are busy and there are new tasks to be processed, the thread pool will create a new thread to process the task. If the number of threads reaches the set maximum, the task will queue up and wait for other tasks to release threads before execution. Thread pool can reduce the creation of threads and save overhead. Look at the chestnut of ThreadPool

static void Main(string[] args)
        {
            for (int i = 1; i <=10; i++)
            {
                //ThreadPool execution task
                ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => {
                    Console.WriteLine($"The first{obj}Execute tasks");
                }),i);
            }
            Console.ReadKey();
        }

The code above performs 10 tasks through ThreadPool

Compared with threads, ThreadPool can reduce the creation of threads and effectively reduce the system overhead; However, ThreadPool cannot control the execution order of threads, nor can we obtain notifications of Thread cancellation / exception / completion in the Thread pool, that is, we cannot effectively monitor and control the threads in the Thread pool.

1 Task creation and running

We know the disadvantages of ThreadPool: we cannot control the execution order of threads in the thread pool, nor can we get the notification of thread cancellation / exception / completion in the thread pool. net4.0 introduces task on the basis of ThreadPool. Task has the advantages of thread pool and solves the disadvantages of using thread pool which is not easy to control.

First, let's take a look at how to create and run a Task. There are five ways to create and execute a Task:

static void Main(string[] args)
        {
            //1. Instantiate a Task in the new mode, which needs to be started through the Start method
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine($"hello, task1 Thread ID by{Thread.CurrentThread.ManagedThreadId}");
            });
            task.Start();

            //2.  Task. Factory. Startnew (action) creates and starts a task     
            Task task2 = Task.Factory.StartNew(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task2 Thread ID by{ Thread.CurrentThread.ManagedThreadId}");
              });

            //3.  Task. Run (action) puts the task in the process pool queue, returns and starts a task
            Task task3 = Task.Run(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task3 Thread ID by{ Thread.CurrentThread.ManagedThreadId}");
              });
            Console.WriteLine("Execute main thread!");
            Console.ReadKey();



            //4. Start a new thread and specify the work method
            Task.Factory.StartNew(Work);
            static void Work()
            {
                _running = true;

                while (_running)
                {
                    //SendOut();
                    //DealWith();
                    Thread.Sleep(50);
                }
            }

            //5. This method can also call UI controls across threads
            Action action = () =>
            {
               UiManager.SurfaceForm.SetSurplusPhotoNumber(count1.ToString());
            };
            Task.Factory.StartNew(action);



        }

We see that "execute main thread" is printed first, and then each Task is printed, which shows that the Task will not block the UI main thread. None of the chestnuts above have a return value. We can also create a Task < tresult > with a return value. The usage is basically the same as that without a return value. Let's simply modify the chestnuts above. The code is as follows:

static void Main(string[] args)
        {
            1.new Instantiate a Task,Need to pass Start Method start
            Task<string> task = new Task<string>(() =>
            {
                return $"hello, task1 of ID by{Thread.CurrentThread.ManagedThreadId}";
            });
            task.Start();

            2.Task.Factory.StartNew(Func func)Create and start a Task
           Task<string> task2 =Task.Factory.StartNew<string>(() =>
            {
                return $"hello, task2 of ID by{ Thread.CurrentThread.ManagedThreadId}";
            });

            3.Task.Run(Func func)Put the task in the process pool queue, return and start a Task
           Task<string> task3= Task.Run<string>(() =>
            {
                return $"hello, task3 of ID by{ Thread.CurrentThread.ManagedThreadId}";
            });

            Console.WriteLine("Execute main thread!");
            Console.WriteLine(task.Result);//Pay attention to task Result blocks the UI main thread when getting results
            Console.WriteLine(task2.Result);
            Console.WriteLine(task3.Result);
            Console.ReadKey();
        }

Pay attention to task When resut obtains the Result, it will block the UI main thread. That is, if the task is not completed, it will wait for the task to complete execution, obtain the Result, and then execute the following code

2. Blocking method of task (Wait/WaitAll/WaitAny)

1. Thread blocking method

When using Thread, we know to use Thread The join () method blocks the UI main Thread. Take an example:

static void Main(string[] args)
        {
            Thread th1 = new Thread(() => {
                Thread.Sleep(500);
                Console.WriteLine("Thread 1 execution completed!");
            });
            th1.Start();
            Thread th2 = new Thread(() => {
                Thread.Sleep(1000);
                Console.WriteLine("Thread 2 execution completed!");
            });
            th2.Start();
            //Blocking UI main thread
            th1.Join();
            th2.Join();
            Console.WriteLine("Main thread execution completed!");
            Console.ReadKey();
        }

2. Wait/WaitAny/WaitAll method of task

Thread's Join method can block the calling thread, but it has some disadvantages: ① if we want to block many threads, each thread must call the Join method once; ② If we want to unblock all threads (or any thread) immediately, it is not easy to use the Join method. Task provides the , Wait/WaitAny/WaitAll , method, which makes it easier to control thread blocking.

  task.Wait() means to wait for the task to finish executing (which will block the UI main thread), and its function is similar to thead Join();   Task.WaitAll(Task[] tasks) means that only all tasks are executed and unblocked;   Task.WaitAny(Task[] tasks) indicates that as long as one task is completed, the blocking will be removed. Look at a chestnut:

static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("Thread 1 execution completed!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("Thread 2 execution completed!");
            });
            task2.Start();
            //Block the main thread. After Task1 and task2 are executed, the main thread can be executed
       //The same functions can be realized by executing [task1.Wait();task2.Wait();]
            Task.WaitAll(new Task[]{ task1,task2});
            Console.WriteLine("Main thread execution completed!");
            Console.ReadKey();
        }

3, Asynchronous method (async/await)

C# 5 introduces a simple method, asynchronous programming. This method makes use of NET Framework 4.5 and later NET Core 5.0 and asynchronous support in Windows runtime

Test environment: vs2019 Net Framework4.6.1.

Let's look at a chestnut:

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Main thread ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
            AsyncTest();

        }
  
        static async void AsyncTest()
        {
            Console.WriteLine("*******Start************ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
            Task<int> taskA = Print();
            Console.WriteLine("*********Middle**********ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
            int a = await taskA;
            Console.WriteLine("return a=" + a);
            Console.WriteLine("*********End**********ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
        }


        static Task<int> Print()
        {
            //Console.WriteLine("Print method starts execution")
            return Task<int>.Run(() =>
            {
                Thread.Sleep(5000);
                return 98;
            });
        }

Reference website:

https://www.cnblogs.com/wyy1234/p/9172467.html#_label1_3

https://www.cnblogs.com/doforfuture/p/6293926.html

https://blog.csdn.net/codingriver/article/details/83342267?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-9.essearch_pc_relevant&spm=1001.2101.3001.4242

Added by mesh2005 on Sun, 19 Dec 2021 18:42:02 +0200