当前位置:   article > 正文

C#多线程和异步编程——Task和async/await详解_c# task不阻塞ui

c# task不阻塞ui

目录

一、什么是异步

二、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的栗子吧

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

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

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

1 Task创建和运行

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

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

  1. static void Main(string[] args)
  2. {
  3. //1. new方式实例化一个Task,需要通过Start方法启动
  4. Task task = new Task(() =>
  5. {
  6. Thread.Sleep(100);
  7. Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");
  8. });
  9. task.Start();
  10. //2. Task.Factory.StartNew(Action action)创建和启动一个Task
  11. Task task2 = Task.Factory.StartNew(() =>
  12. {
  13. Thread.Sleep(100);
  14. Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
  15. });
  16. //3. Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
  17. Task task3 = Task.Run(() =>
  18. {
  19. Thread.Sleep(100);
  20. Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
  21. });
  22. Console.WriteLine("执行主线程!");
  23. Console.ReadKey();
  24. //4. 开启一个新线程,指定work方法
  25. Task.Factory.StartNew(Work);
  26. static void Work()
  27. {
  28. _running = true;
  29. while (_running)
  30. {
  31. //SendOut();
  32. //DealWith();
  33. Thread.Sleep(50);
  34. }
  35. }
  36. //5. 这种方法也可以跨线程调用UI控件
  37. Action action = () =>
  38. {
  39. UiManager.SurfaceForm.SetSurplusPhotoNumber(count1.ToString());
  40. };
  41. Task.Factory.StartNew(action);
  42. }

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

  1. static void Main(string[] args)
  2. {
  3. 1.new方式实例化一个Task,需要通过Start方法启动
  4. Task<string> task = new Task<string>(() =>
  5. {
  6. return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";
  7. });
  8. task.Start();
  9. 2.Task.Factory.StartNew(Func func)创建和启动一个Task
  10. Task<string> task2 =Task.Factory.StartNew<string>(() =>
  11. {
  12. return $"hello, task2的ID为{ Thread.CurrentThread.ManagedThreadId}";
  13. });
  14. 3.Task.Run(Func func)将任务放在线程池队列,返回并启动一个Task
  15. Task<string> task3= Task.Run<string>(() =>
  16. {
  17. return $"hello, task3的ID为{ Thread.CurrentThread.ManagedThreadId}";
  18. });
  19. Console.WriteLine("执行主线程!");
  20. Console.WriteLine(task.Result);//注意task.Result获取结果时会阻塞UI主线程
  21. Console.WriteLine(task2.Result);
  22. Console.WriteLine(task3.Result);
  23. Console.ReadKey();
  24. }

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

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

1 Thread阻塞线程的方法

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

  1. static void Main(string[] args)
  2. {
  3. Thread th1 = new Thread(() => {
  4. Thread.Sleep(500);
  5. Console.WriteLine("线程1执行完毕!");
  6. });
  7. th1.Start();
  8. Thread th2 = new Thread(() => {
  9. Thread.Sleep(1000);
  10. Console.WriteLine("线程2执行完毕!");
  11. });
  12. th2.Start();
  13. //阻塞UI主线程
  14. th1.Join();
  15. th2.Join();
  16. Console.WriteLine("主线程执行完毕!");
  17. Console.ReadKey();
  18. }

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执行完毕就解除阻塞,看一个栗子: 

  1. static void Main(string[] args)
  2. {
  3. Task task1 = new Task(() => {
  4. Thread.Sleep(500);
  5. Console.WriteLine("线程1执行完毕!");
  6. });
  7. task1.Start();
  8. Task task2 = new Task(() => {
  9. Thread.Sleep(1000);
  10. Console.WriteLine("线程2执行完毕!");
  11. });
  12. task2.Start();
  13. //阻塞主线程。task1,task2都执行完毕再执行主线程
  14.        //执行【task1.Wait();task2.Wait();】可以实现相同功能
  15. Task.WaitAll(new Task[]{ task1,task2});
  16. Console.WriteLine("主线程执行完毕!");
  17. Console.ReadKey();
  18. }

三、异步方法(async/await,配对使用)

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

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

示例1:

  1. private void button1_Click(object sender, EventArgs e)
  2. {
  3. Console.WriteLine("主线程ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
  4. AsyncTest();
  5. }
  6. static async void AsyncTest()
  7. {
  8. Console.WriteLine("*******Start************ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
  9. Task<int> taskA = Print();
  10. Console.WriteLine("*********Middle**********ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
  11. int a = await taskA;
  12. Console.WriteLine("return a=" + a);
  13. Console.WriteLine("*********End**********ManagedThreadId:" + Thread.CurrentThread.ManagedThreadId);
  14. }
  15. static Task<int> Print()
  16. {
  17. //Console.WriteLine("Print方法开始执行")
  18. return Task<int>.Run(() =>
  19. {
  20. Thread.Sleep(5000);
  21. return 98;
  22. });
  23. }

示例2

  1. private async void button_Click(Object sender,EventArgs e)
  2. {
  3. TextBox1.text=await testString();
  4. }
  5. private async Task<string> testString()
  6. {
  7. return await Task<string>.Run)(()=>
  8. {
  9. Thread.Sleep(5000);
  10. return "test success";
  11. });
  12. }

参考网址:

C#多线程和异步(二)——Task和async/await详解 - 捞月亮的猴子 - 博客园

C#中 Thread,Task,Async/Await,IAsyncResult 的那些事儿! - Mr靖 - 博客园

C# async与await的使用说明_codingriver的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/104543?site
推荐阅读
相关标签
  

闽ICP备14008679号