赞
踩
目录
2 Task的阻塞方法(Wait/WaitAll/WaitAny)
同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。
异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,让异步编程更为方便。本篇主要介绍Task、async/await相关的内容,其他异步操作的方式会在下一篇介绍。
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不能控制线程的执行顺序,我们也不能获取线程池内线程取消/异常/完成的通知,即我们不能有效监控和控制线程池中的线程。
我们知道了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,然后再执行后边的代码
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();
- }
C# 5 引入了一种简便方法,即异步编程。此方法利用了 .NET Framework 4.5 及更高版本、.NET Core 5.0 和 Windows 运行时中的异步支持.
测试环境:vs2019,.Net Framework4.6.1。
示例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;
- });
- }
示例2:
- private async void button_Click(Object sender,EventArgs e)
- {
- TextBox1.text=await testString();
- }
- private async Task<string> testString()
- {
-
- return await Task<string>.Run)(()=>
- {
- Thread.Sleep(5000);
- return "test success";
-
- });
- }
参考网址:
C#多线程和异步(二)——Task和async/await详解 - 捞月亮的猴子 - 博客园
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。