欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C#多线程和异步编程——Task和async/await详解

程序员文章站 2022-06-10 18:06:54
...

目录

一、什么是异步

二、Task介绍

1 Task创建和运行

 2 Task的阻塞方法(Wait/WaitAll/WaitAny)

三、异步方法(async/await)


一、什么是异步

  同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。

  异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,让异步编程更为方便。本篇主要介绍Task、async/await相关的内容,其他异步操作的方式会在下一篇介绍。

 

二、Task介绍

  Task是在ThreadPool的基础上推出的,我们简单了解下ThreadPool。ThreadPool中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务,如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。线程池能减少线程的创建,节省开销,看一个ThreadPool的栗子吧

static void Main(string[] args)
        {
            for (int i = 1; i <=10; i++)
            {
                //ThreadPool执行任务
                ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => {
                    Console.WriteLine($"第{obj}个执行任务");
                }),i);
            }
            Console.ReadKey();
        }

  上边的代码通过ThreadPool执行了10个任务

   ThreadPool相对于Thread来说可以减少线程的创建,有效减小系统开销;但是ThreadPool不能控制线程的执行顺序,我们也不能获取线程池内线程取消/异常/完成的通知,即我们不能有效监控和控制线程池中的线程。

1 Task创建和运行

  我们知道了ThreadPool的弊端:我们不能控制线程池中线程的执行顺序,也不能获取线程池内线程取消/异常/完成的通知。net4.0在ThreadPool的基础上推出了Task,Task拥有线程池的优点,同时也解决了使用线程池不易控制的弊端。

首先看一下怎么去创建并运行一个Task,Task的创建和执行方式有如下5种:

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();



            //4.  开启一个新线程,指定work方法
            Task.Factory.StartNew(Work);
            static void Work()
            {
                _running = true;

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

            //5. 这种方法也可以跨线程调用UI控件
            Action action = () =>
            {
               UiManager.SurfaceForm.SetSurplusPhotoNumber(count1.ToString());
            };
            Task.Factory.StartNew(action);



        }

 我们看到先打印"执行主线程",然后再打印各个任务,说明了Task不会阻塞UI主线程。上边的栗子Task都没有返回值,我们也可以创建有返回值的Task<TResult>,用法和没有返回值的基本一致,我们简单修改一下上边的栗子,代码如下:

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);//注意task.Result获取结果时会阻塞UI主线程
            Console.WriteLine(task2.Result);
            Console.WriteLine(task3.Result);
            Console.ReadKey();
        }

注意task.Resut获取结果时会阻塞UI主线程,即如果task没有执行完成,会等待task执行完成获取到Result,然后再执行后边的代码

 2 Task的阻塞方法(Wait/WaitAll/WaitAny)

1 Thread阻塞线程的方法

  使用Thread时,我们知道用thread.Join()方法即可阻塞UI主线程。看一个例子:

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();
            //阻塞UI主线程
            th1.Join();
            th2.Join();
            Console.WriteLine("主线程执行完毕!");
            Console.ReadKey();
        }

2 Task的Wait/WaitAny/WaitAll方法

   Thread的Join方法可以阻塞调用线程,但是有一些弊端:①如果我们要实现很多线程的阻塞时,每个线程都要调用一次Join方法;②如果我们想让所有的线程执行完毕(或者任一线程执行完毕)时,立即解除阻塞,使用Join方法不容易实现。Task提供了  Wait/WaitAny/WaitAll  方法,可以更方便地控制线程阻塞。

  task.Wait()  表示等待task执行完毕(会阻塞UI主线程),功能类似于thead.Join();  Task.WaitAll(Task[] tasks)  表示只有所有的task都执行完成了再解除阻塞;  Task.WaitAny(Task[] tasks) 表示只要有一个task执行完毕就解除阻塞,看一个栗子: 

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();
        }

三、异步方法(async/await)

  C# 5 引入了一种简便方法,即异步编程。此方法利用了 .NET Framework 4.5 及更高版本、.NET Core 5.0 和 Windows 运行时中的异步支持.

测试环境:vs2019,.Net Framework4.6.1。

我们看一个栗子:

        private void button1_Click(object sender, EventArgs e)
        {
            Console.WriteLine("主线程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方法开始执行")
            return Task<int>.Run(() =>
            {
                Thread.Sleep(5000);
                return 98;
            });
        }

参考网址:

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

相关标签: c#和dot net