【Unity】【CSharp】多线程
Task和Thread的区别
基于不同的 .NET 框架:Thread 是基于 Windows 操作系统提供的 API 实现,而 Task 则是基于 .NET框架提供的 TPL(Task Parallel Library)实现。
默认执行线程池:Thread 默认使用前台线程,而 Task 默认使用后台线程。这意味着,Thread 会阻塞主线程,而 Task不会。
异步执行:Task 支持异步执行,而 Thread 不支持。这意味着,在使用 Task 时,可以通过 await 和 async关键字轻松实现异步编程,而 Thread 则需要手动管理线程的启动和等待。
异常处理:Task 提供了更好的异常处理机制,可以将异常传递给调用方,而 Thread 则需要在每个线程中处理异常。
任务调度器:Task 提供了任务调度器(TaskScheduler),可以控制任务的并发性和调度方式,而 Thread 则没有这个功能。
返回值:Task 可以有返回值,而 Thread 没有。这是因为 Task 是基于 TPL 实现的,可以利用 .NET框架提供的并发编程模型来实现任务之间的依赖和调度。
Task
Task
和Task<T>
的创建
Task的创建
使用Task
的构造函数
1 | Task task = new Task(() => { |
使用Task.Run
的构造函数
1 | Task task = Task.Run(() => { |
Task<T>
的创建
Task<T>
与会返回一个类型为T的结果
使用Task
的构造函数
1 | Task<int> task = new Task<int>(() =>{ |
使用Task.Run
的构造函数
1 | Task<int> task = Task.Run(() => { |
启动和等待Task
和Task<T>
启动
使用start
方法启动
1 | task.Start(); |
使用Wait()
阻塞等待
直接控制Task
,实现异步等待任务的完成
会阻塞主线程,类似于thread1.Join()
1 | task.Wait(); // 阻塞当前线程,等待任务完成 |
使用WhenAll
和WhenAny
控制线程
不会阻塞主线程
1 | Task.WhenAll(task1, task2).ContinueWith((t) => { Console.Writeline("执行异步代码"); }); // 当task1和task2执行完毕后,再执行后续代码 |
使用await
等待Task
和Task<T>
在异步代码中使用await
等待其他的任务完成(为.net5.0推出的方法)
不会阻塞主线程
1 | using System; |
运行结果
1 | 主线程开始 |
三个等待的区别
Wait()
针对线程操作,会阻塞主线程WhenAll
针对线程操作,不会阻塞主线程await
在线程中针对其他线程操作,不会阻塞主线程
Thread
1 | using System.Threading; |
此时的输出结果是不可控的,可能先执行A,也可能先执行B,这个是操作系统根据CPU自动计算出来的。
而且A和B是会嵌套交叉执行的
如何让程序先执行A,执行完A之后在执行B;或者先执行完B再执行A:使用lock关键字
lock关键字
可以通过lock关键字来控制A和B的执行顺序。使用同一个lock参数的代码,程序会等待前面的代码执行完之后再执行后面的
1 | using System.Threading; |
此时可能会先执行A,执行完A后再执行B;也有可能先执行B,执行完B之后再执行A。C函数没有被锁住,所以他能出现在任意位置。
补充:这里的o是Object类(基类)。所以,lock的参数可以是任意的类
拓展
在unity中将子线程的代码转移到主线程中执行
1 | using UnityEngine; |
在其他函数中可以通过调用RunOnMainThread()
函数将方法转移到主线程上执行
常用与数据请求上,接收到的数据一般都是在子线程上。但是在unity的子线程中无法访问transform属性等,故需要转移到主线程上执行
1 | RunOnMainThread(() => |