Simple arrangement of Task, async and await, and Thread basics used in Unity C#

Simple arrangement of Task, async and await, and Thread basics used in Unity C#

Table of contents

Simple arrangement of Task, async and await, and Thread basics used in Unity C#

1. Basic concepts of Task, async and await, Thread

1. Thread, multi-thread

2、Task

 3、async (await )

2. Basic use of Task, creation and startup of Task

1. Create and run a Task

2. Create a Task with a return value

3. Task provides task.RunSynchronously() for synchronous execution of Task tasks

3. Task blocking method (Wait/WaitAll/WaitAny)

1. Thread blocks the main thread method: thread.Join() method

2. Task provides Wait/WaitAny/WaitAll methods, which can control thread blocking more conveniently

4. The continuation operation of Task (WhenAny/WhenAll/ContinueWith)

1. Task continuation operation (WhenAny/WhenAll/ContinueWith)

 2. The continuation operation of Task.Factory (WhenAny/WhenAll/ContinueWith)

5. Task task cancellation (CancellationTokenSource)

1. Thread task cancellation

 2. Task task cancellation

3、 CancellationTokenSource  中 source.Token.Register 和 source.CancelAfter(XXXXms)

 5. Asynchronous method (async/await)

Six, Task, async and await, Thread simple summary


1. Basic concepts of Task, async and await, Thread

1. Thread, multi-thread

Thread (Thread) is the basic execution unit in a process and the basic unit for the operating system to allocate CPU time. A process can contain several threads, and the first thread executed at the process entry is regarded as the main thread of the process.

In .NET applications, the Main() method is used as the entry point. When this method is called, the system will automatically create a main thread. Threads are mainly composed of CPU registers, call stacks, and thread local storage (Thread Local Storage, TLS) .

The CPU register mainly records the status of the currently executing thread, the call stack is mainly used to maintain the memory and data called by the thread, and the TLS is mainly used to store the status information of the thread.

Multithreading: Multiple tasks can be completed at the same time; the program can respond faster; tasks that take up a lot of processing time or tasks that are not currently being processed can periodically give up processing time to other tasks; tasks can be stopped at any time; Set the priority of each task to optimize program performance.

class Program
    {
        static void Main(string[] args)
        {
            //创建无参的线程
            Thread thread1 = new Thread(new ThreadStart(Thread1));
            //调用Start方法执行线程
            thread1.Start();

            Console.ReadKey();
        }

        /// <summary>
        /// 创建无参的方法
        /// </summary>
        static void Thread1()
        {
            Console.WriteLine("这是无参的方法");
        }
    }

2、Task

Task is launched on the basis of ThreadPool. Let's briefly understand ThreadPool.

  • There are a certain number of threads in the ThreadPool. If there is a task 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 will be recycled by the thread pool for subsequent tasks.
  • When all the threads in the thread pool are busy and there is a new task to be processed, the thread pool will create a new thread to process the task
  • If the number of threads reaches the maximum value set, the task will be queued and wait for other tasks to release threads before execution.
  • The thread pool can reduce the creation of threads and save overhead. See the use of a ThreadPool as follows
 static void Main(string[] args)
 {
       for (int i = 1; i <=10; i++)
       {
                //ThreadPool执行任务
                ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => {
                    Console.WriteLine($"第{obj}个执行任务");
                }),i);
       }
     Console.ReadKey();
}

The above code executes 10 tasks through ThreadPool, and the execution result is

 3、async (await )

async  is mainly used to modify methods.

When a method is called, the caller needs to wait for the method to complete and return before continuing . We call this method a synchronous method;

When a method is called, it returns immediately and obtains a thread to execute the internal business of the method. The caller does not need to wait for the method to complete. We call this method an asynchronous method.

The advantage of asynchrony is that it is non-blocking (the calling thread will not suspend execution to wait for the sub-thread to complete), so we set some time-consuming tasks that do not need to use the results immediately to be executed asynchronously, which can improve the running efficiency of the program.

Async and await keywords in C# , asynchronous programming is very popular.

1) When we're dealing with the UI, we're using a long-running method when a button is clicked, like reading a large file or something else that takes a long time

2) In this case the whole application has to wait for the whole task to finish.

3) In other words, if any process in the synchronous application is blocked, the entire application is blocked and our application stops responding until the entire task is completed.

Asynchronous programming is very useful in this situation. By using asynchronous programming, the application can continue to do other work that does not depend on the completion of the entire task.

Net4.0 launched the Task class on the basis of ThreadPool . Microsoft strongly recommends using Task to execute asynchronous tasks. Now the asynchronous methods in the C# class library basically use Task;

net5.0 introduced async/await to make asynchronous programming more convenient.

2. Basic use of Task, creation and startup of Task

We know the disadvantages of ThreadPool:

(1) We cannot control the execution order of threads in the thread pool,

(2) It is also impossible to obtain notifications of thread cancellation/abnormality/completion in the thread pool.

net4.0 launched Task on the basis of ThreadPool

(1) Task has the advantages of thread pool

(2) At the same time, it also solves the disadvantages of using the thread pool that is not easy to control

1. Create and run a Task

Let's first look at how to create and run a Task. There are three ways to create and start a Task:

  • Task task = new Task(); task.Start();
  • Task task = Task.Factory.StartNew();
  • Task task = Task.Run();
 		 static void Main(string[] args)
        {
            //1.new方式实例化一个Task,需要通过Start方法启动
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");
            });
            task.Start();

            //2.Task.Factory.StartNew(Action action)创建和启动一个Task
            Task task2 = Task.Factory.StartNew(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
              });

            //3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
            Task task3 = Task.Run(() =>
              {
                  Thread.Sleep(100);
                  Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
              });
            Console.WriteLine("执行主线程!");
            Console.ReadKey();
        }

The running results are as follows. We can see that "execute main thread" is printed first, and then each task is printed, which shows that Task will not block the main thread:

2. Create a Task with a return value

In the code above, the Task has no return value. We can also create a Task with a return value . The usage is basically the same as that without a return value. Let’s simply modify the chestnut above. The code is as follows:

 Task<data type>, task.Result gets the result

It is worth noting that: task.Resut will block the thread when getting the result, that is, if the task is not executed, it will wait for the task to complete and get the Result, and then execute the following code

  • Task<string> task = new Task(); task.Start();
  • Task<string> task = Task.Factory.StartNew();
  • Task<string> task = Task.Run();
         static void Main(string[] args)
        {
            //1.new方式实例化一个Task,需要通过Start方法启动
            Task<string> task = new Task<string>(() =>
            {
                return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";
            });
            task.Start();

            //2.Task.Factory.StartNew(Func func)创建和启动一个Task
           Task<string> task2 =Task.Factory.StartNew<string>(() =>
            {
                return $"hello, task2的ID为{ Thread.CurrentThread.ManagedThreadId}";
            });

            //3.Task.Run(Func func)将任务放在线程池队列,返回并启动一个Task
           Task<string> task3= Task.Run<string>(() =>
            {
                return $"hello, task3的ID为{ Thread.CurrentThread.ManagedThreadId}";
            });

            Console.WriteLine("执行主线程!");
            Console.WriteLine(task.Result);
            Console.WriteLine(task2.Result);
            Console.WriteLine(task3.Result);
            Console.ReadKey();
        }

The result of the operation is as follows:

3. Task provides task.RunSynchronously() for synchronous execution of Task tasks

The execution of Task in all the above codes is asynchronous and will not block the main thread. In some scenarios, what should we do if we want Task to execute synchronously? Task provides task.RunSynchronously() for executing Task tasks synchronously

  • Task task = new Task(); task.RunSynchronously();
        static void Main(string[] args)
        {
            Task task = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("执行Task结束!");
            });
            //同步执行,task会阻塞主线程
            task.RunSynchronously();
            Console.WriteLine("执行主线程结束!");
            Console.ReadKey();
        }

The result of the operation is as follows:

3. Task blocking method (Wait/WaitAll/WaitAny)

1. Thread blocks the main thread method: thread.Join() method

Thread's Join method can block the calling thread , but there are some disadvantages:

If we want to implement blocking of many threads, each thread must call the Join method once;

②If we want to unblock immediately when all threads are executed (or any thread is executed), it is not easy to use the Join method.

The reference code is as follows

          static void Main(string[] args)
        {
            Thread th1 = new Thread(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            th1.Start();
            Thread th2 = new Thread(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            th2.Start();
            //阻塞主线程
            th1.Join();
            th2.Join();
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

The result of the operation is as follows:

2. Task provides Wait/WaitAny/WaitAll methods, which can control thread blocking more conveniently

  • Task.Wait() means waiting for the task to be executed, and its function is similar to thead.Join();
  • Task.WaitAll(Task[] tasks) means that only when all tasks are executed, the block will be unblocked;
  • Task.WaitAny ( Task[] tasks ) means that as long as one task is executed, it will be unblocked;

The reference code is as follows:

        static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //阻塞主线程。task1,task2都执行完毕再执行主线程
            //执行【task1.Wait();task2.Wait();】可以实现相同功能
            Task.WaitAll(new Task[] { task1, task2 });
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

The result of the operation is as follows

4. The continuation operation of Task (WhenAny/WhenAll/ContinueWith)

1. Task continuation operation (WhenAny/WhenAll/ContinueWith)

The return value of the above Wait/WaitAny/WaitAll methods is void, and these methods simply implement blocking threads .

We now think: After all tasks are executed (or any task is executed), start to perform follow-up operations, how to achieve it?

At this time, the WhenAny/WhenAll method can be used, and these methods return a task instance after execution.

  • task.WhenAll(Task[] tasks) indicates that all tasks are executed before performing subsequent operations
  • task.WhenAny(Task[] tasks) Indicates that after any task is executed, subsequent operations will be performed

The reference code is as follows:

        static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //task1,task2执行完了后执行后续操作
            Task.WhenAll(task1, task2).ContinueWith((t) => {
                Thread.Sleep(100);
                Console.WriteLine("执行后续操作完毕!");
            });

            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

The result of the operation is as follows:

 2. The continuation operation of Task.Factory (WhenAny/WhenAll/ContinueWith)

The above chestnuts can also be implemented by Task.Factory.ContinueWhenAll(Task[] tasks, Action continuationAction) and Task.Factory.ContinueWhenAny(Task[] tasks, Action continuationAction)

Modify the above code as follows

        static void Main(string[] args)
        {
            Task task1 = new Task(() => {
                Thread.Sleep(500);
                Console.WriteLine("线程1执行完毕!");
            });
            task1.Start();
            Task task2 = new Task(() => {
                Thread.Sleep(1000);
                Console.WriteLine("线程2执行完毕!");
            });
            task2.Start();
            //通过TaskFactroy实现
            Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>
            {
                Thread.Sleep(100);
                Console.WriteLine("执行后续操作");
            });

            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

The running result remains unchanged, as follows

5. Task task cancellation (CancellationTokenSource)

1. Thread task cancellation

Before Task, we used Thread to execute the task. How does Thread cancel the task?

The general process is:

  • 1) Set a variable to control whether the task stops, such as setting a variable isStop
  • 2) Then the thread polls to check isStop, and stops if isStop is true

The reference code is as follows:

        static void Main(string[] args)
        {
            bool isStop = false;
            int index = 0;
            //开启一个线程执行任务
            Thread th1 = new Thread(() =>
            {
                while (!isStop)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"第{++index}次执行,线程运行中...");
                }
            });
            th1.Start();
            //五秒后取消任务执行
            Thread.Sleep(5000);
            isStop = true;
            Console.ReadKey();
        }

The result of the operation is as follows

 2. Task task cancellation

There is a special class CancellationTokenSource in Task to cancel task execution

Still using the above example, adjust the code as follows

        static void Main(string[] args)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            int index = 0;
            //开启一个task执行任务
            Task task1 = new Task(() =>
            {
                while (!source.IsCancellationRequested)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"第{++index}次执行,线程运行中...");
                }
            });
            task1.Start();
            //五秒后取消任务执行
            Thread.Sleep(5000);
            //source.Cancel()方法请求取消任务,IsCancellationRequested会变成true
            source.Cancel();
            Console.ReadKey();
        }

The effect of the program running remains unchanged, as follows

3、 CancellationTokenSource  中 source.Token.Register 和 source.CancelAfter(XXXXms)

The function of CancellationTokenSource is not only to cancel task execution , we can use

  • source.CancelAfter(5000) automatically cancels the task after 5 seconds
  • source.Token.Register(Action action) can register the callback function triggered by canceling the task, that is, the registered action will be executed when the task is canceled

The reference code is as follows

        static void Main(string[] args)
        {
            CancellationTokenSource source = new CancellationTokenSource();
            //注册任务取消的事件
            source.Token.Register(() =>
            {
                Console.WriteLine("任务被取消后执行xx操作!");
            });

            int index = 0;
            //开启一个task执行任务
            Task task1 = new Task(() =>
            {
                while (!source.IsCancellationRequested)
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"第{++index}次执行,线程运行中...");
                }
            });
            task1.Start();
            //延时取消,效果等同于Thread.Sleep(5000);source.Cancel();
            source.CancelAfter(5000);
            Console.ReadKey();
        }

The result of the operation is as follows

 5. Asynchronous method (async/await)

The appearance of async and await in C# 5.0 makes asynchronous programming easier.

This approach takes advantage of the asynchronous support in .NET Framework 4.5 and later, .NET Core, and the Windows Runtime.

The compiler does the hard work that developers have done, and the application retains a logical structure similar to synchronous code.

  • async is used in front of the method definition, and await can only be written in methods marked with async.
  • Pay attention to where await waits asynchronously. The code behind await may be executed on a different thread than the previous code.
  • The async keyword creates a state machine, similar to the yield return statement; await will unblock the current thread and complete other tasks

Here is how to read file content synchronously and asynchronously.

We can see that the asynchronous read code is basically the same as the synchronous read code. async/await makes asynchronous coding easier, we can write asynchronous code like synchronous code.

Pay attention to a small problem: the return value of the method signature in the asynchronous method is Task, and the return value in the code is T.

The signature return value of GetContentAsync in the code is Task, while the return value in the code is string. Keeping this detail in mind is helpful when analyzing asynchronous code.

The return value of the asynchronous method signature has the following three types:

  • Task: If the calling method wants to obtain a return value of type T by calling an asynchronous method, the signature must be Task;
  • Task: If the calling method does not want to obtain a value through the asynchronous method, but just wants to track the execution status of the asynchronous method, then we can set the return value of the asynchronous method signature as Task;
  • void: If the calling method is only to call the asynchronous method and does not interact with the asynchronous method, we can set the return value of the asynchronous method signature to void, which is also called "call and forget".

The reference code is as follows

        static void Main(string[] args)
        {
            //Console.WriteLine(" Environment.CurrentDirectory "+ Environment.CurrentDirectory);
            string content = GetContentAsync(Environment.CurrentDirectory + @"/test.txt").Result;
            //调用同步方法
            //string content = GetContent(Environment.CurrentDirectory + @"/test.txt");
            Console.WriteLine(content);
            Console.ReadKey();
        }
 
        /// <summary>
        /// 异步读取文件内容
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        async static Task<string> GetContentAsync(string filename)
        {

            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //ReadAync方法异步读取内容,不阻塞线程
            Console.WriteLine("开始读取文件");
            int len = await fs.ReadAsync(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }

        /// <summary>
        /// 同步读取文件内容
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        static string GetContent(string filename)
        {
            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //Read方法同步读取内容,阻塞线程
            int len = fs.Read(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }

The result of the operation is as follows:

Six, Task, async and await, Thread simple summary

Finally, we simply remember some features, so we can have a better understanding of them:

  • 1) async/await is based on Task
  • 2) Task is an improvement on the encapsulation of ThreadPool, mainly for more effective control of threads in the thread pool (threads in ThreadPool, it is difficult for us to control their execution order, task continuation and cancellation, etc. through code);
  • 3) ThreadPool is based on Thread, the main purpose is to reduce the number of Thread creation and the cost of managing Thread.
  • 4) async/await Task is more advanced in C#, and it is also a feature promoted by Microsoft
  • 5) We can try to use Task instead of Thread/ThreadPool during development. To handle local IO and network IO tasks, try to use async/await to improve task execution efficiency.

Guess you like

Origin blog.csdn.net/u014361280/article/details/132401320