Unity中的异步编程【1】—— Unity与async 、 await
新手在Unity里写东西,一个方法的内容如果写复杂了,容易把Uinty写死,就会卡帧,用流行的话来总结就是:在Update里面活生生把天聊死了。
此外,如果新手才入门,不擅长使用消息、事件来进行异步的统筹,一门心思在Update里面实现各种有延时有顺序的步骤,于是自己把自己绕晕不说,程序又难调试,难修改,更甚——帧率低下…
那么用协程来实现异步…聪明…
但,还有比协程更好用的异步框架,那就是UniTask
一、什么是异步:async和await是啥
异步这个概念一言难尽,里面涉及到很多上古时候的知识:单核/多核,单线程/多线程,并发/并行,阻塞/非阻塞…
举一个例子:
小赵夫妻响应祖国的号召,夫妻二人7年之内哗啦啦生了3个娃娃。对这对夫妻来说,他们生娃只能一胎一胎的生,所以小赵家生三娃这件事情就是单线程,而且是阻塞式的。
又过了几年,人们纷纷进城,村里的人越来越少,于是大家响应号召要多多生娃,特别是赵庄的赵太爷在县太爷面前拍了胸脯:2年之内,我们赵庄要增加100个新生儿。
于是赵太爷在村里做了动员,大伙儿也积极响应,纷纷备孕。于是赵庄生100个娃这件事情变成了多线程,而且是非阻塞的(大家一起生——这叫多线程,张家怀孕不会影响李家怀孕——这叫非阻塞)。
二、C#(.Net)中的异步
.Net自C#5开始,大概2012年前后,开始引入了异步编程(async和await),有了异步之后,很少需要手动去开线程了,而且业务更好理解,代码更加清晰。
同步举例:解放前的辛家庄有十户人,他们共用一个灶做饭,于是只能轮着顺序做饭…
以上做饭的总耗时为10
代码清单
using System;
using System.Threading.Tasks; //关键的包
public static void Cooking(int i ){
Console.WriteLine($"第{i}户家庭开始做饭...");
Task.Delay(1000).Wait();
Console.WriteLine($"第{i}户家庭做饭结束");
}
public static void TestCooking(){
for(int i = 0;i<10;i++)
{
Cooking(i);
}
}
TestCooking();
异步举例:现在他们富裕了,每家都有一个灶,于是做饭的时候全村炊烟袅袅,大家各做各的,这做饭就成了并发(并行)。
张家做饭和李家做饭和不干涉,互不阻碍。
以下是他们十家一起做饭的壮观情景:
请注意红框中的【async】和【await】关键字
各家在各家做饭,虽然同一时间开始做,但总耗时为1
代码清单
public static async Task Cooking(int i ){
Console.WriteLine($"第{i}户家庭开始做饭...");
await Task.Delay(1000);
Console.WriteLine($"第{i}户家庭做饭结束");
}
public static async Task TestCooking(){
var allTasks = new List<Task>();
for(int i = 0;i<10;i++)
{
allTasks.Add(Cooking(i));
}
await Task.WhenAll(allTasks.ToArray());
}
await TestCooking();
三、 Unity中的异步
- 问题的提出:点了按钮,隔三秒启动发动机,发动机转4秒,然后停机。以上内容把它写在一个方法里面。
解决办法1:一般大家能够想到的就是协程,没问题,这是Unity标准玩法,但是协程没有返回值,也不能好好调试…
解决办法2:聪明的你肯定想到.NET的 async和await了,但是,不好意思,Unity不能完美支持,比如在WebGL里面,就不支持多线程,async和await背后是多线程。所以,此路不通。
解决办法3:UniRX和UniTask
UniRX是上世代的产物,响应式编程兴起的时候创建的项目,至今已经进入稳态,官网特别说明,如果你要搞异步编程,那么就用UniTask,因为异步功能已经从UniRX拆分到UniTask了。
用UniTask完成以上业务:
点击【Button】后,启动发动机…停止发动机:
代码清单:
using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;//关键包
public class mytest : MonoBehaviour
{
public Button myBtn;
// Start is called before the first frame update
void Start()
{
myBtn.onClick.AddListener(async ()=>
{
//隔三秒启动发动机,发动机转4秒,然后停机
Debug.Log($"{Time.realtimeSinceStartup}: 预热三秒");
await UniTask.Delay(System.TimeSpan.FromSeconds(3),ignoreTimeScale:false);
Debug.Log($"{Time.realtimeSinceStartup}: 启动发动机,发动机开始运转");
await UniTask.Delay(System.TimeSpan.FromSeconds(4), ignoreTimeScale: false);
Debug.Log($"{Time.realtimeSinceStartup}: 发动机停机");
});
}
}