异步方案
逻辑放在 Update / LateUpdate
协程 :通过开启协程进行异步操作
消耗大:每帧消耗40kb GC
与主线程在同一核心
依托于 Monobehavior
C# 内置 async / await Task
不需要依赖 Monobehavior
但涉及到多线程分配,不符合 Unity 设计,且消耗大
Unity Task
不依赖 Monobehavior
在主线程中与 Unity 协同
实现协程的效果,且不产生 GC消耗
异步网络资源需要手动释放,不释放会一直调用
协程
协程的本质是迭代器,协程函数的调用会实例化一个接口对象
IEnumerator 类是函数对象容器,依次执行容器里面的函数,也就是不断执行注册给这个委托的函数
每执行完一次有一个当前的返回值,yeild return返回
等待时间返回 waitsecond类,通过这个类里面的计时器,每帧更新计时器
yeild抽出两个yeild之间的函数代码放到IEnumerator容器中
每帧都要判断(每个Update执行一次IEnumerator里面的函数),延迟执行代码 / 等待某个操作完成之后执行后面的代码
典型的回调例子,每帧等待条件完成传回再接着执行后面的
返回值
StartCoroutine(Fun(element a)):返回 Coroutine 对象保存协程
IEnumerator Fun(element a):返回 IEnumerator 迭代器接口,声明协程
yield break:结束协程
常用等待
yield return null :下一帧再执行后续代码
yield return new WaitForSeconds(.1f):等待0.1s后执行
yield return new WaitUntil(() => frame >= 10):每帧都会执行提供的委托,等待委托为true执行
原理
1 2 3 4 5 public class YieldInstruction { public IEnumerator ie; public float executeTime; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class CoroutineMgr : MonoBehaviour { private List<YieldInstruction> list = new List<YieldInstruction>(); public void StartCoroutine (IEnumerator ie ) { ie.MoveNext(); if ((ie.Current is null ) || (ie.Current is int )) { list.Add(new YieldInstruction{ ie=ie,executeTime=0 ; }); } else if (ie.Current is WaitForSeconds) { list.Add(new YieldInstruction{ ie=ie, executeTime=Time.time+(ie.Currentas WaitForSeconds).second }); } else if (...) {...} } void Update () { for (int i=list.Count-1 ; i>=0 ; i--) { if (list[i].executeTime<=Time.time) { if (list[i].ie.MoveNext()) { if ((ie.Current is null ) || (ie.Current is int )) || (ie.Current is WaitForSeconds)) { } else { list.RemoveAt(i); } } else { list.RemoveAt(i); } } } } }
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class SomeClass : MonoBehaviour { public float smoothing = 1f ; public Transform target; void Start () { StartCoroutine(MyCoroutine(target)); } IEnumerator MyCoroutine (Transform target ) { while (Vector3.Distance(transform.position, target.position) > 0.05f ) { transform.position = Vector3.Lerp(transform.position, target.position, smoothing * Time.deltaTime); yield return null ; } print("Reach the Target!" ); yield return new WaitForSeconds (3f ) ; print("MyCoroutine is finished!" ); } }
判空与强制停止
将协程用 Coroutine 储存,每次协程结束置空,判空变量判断是否当前在执行
若需要重新执行就中断之前的协程,判空之后停止之前的
1 2 3 4 5 6 7 8 9 10 11 12 private Coroutine _coro; if (_coro != null ){ StopCorotine(_coro); } _coro = startCoroutine(Fun); private IEnumerator Fun (){ _coro = null ; }
async / await Task
可以用在PC和安卓,在WebGL会报错
UI单线程会线程阻塞,真正多线程的处理方式 IJobSystem
从Thread到Task
1 2 3 4 5 6 7 8 9 10 private void ButtonEvent (){ Thread t = new Thread(()=>{ Thread.Sleep(3000 ); MessageBox.Show("做素菜" ); Thread.Sleep(5000 ); MessageBox.Show("做荤菜" ); }); t.Start(); }
1 2 3 4 5 6 7 8 9 private void ButtonEvent (){ await Task.Run(()=>{ Thread.Sleep(3000 ); MessageBox.Show("做素菜" ); Thread.Sleep(5000 ); MessageBox.Show("做荤菜" ); }) }
关键字
示例
1 using system.Threading.Tasks;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Mover { public async Task Move (Transform transform ) { await Task.Delay(TimeSpan.FromSecond(0.2f )); transform.position += Vector3.right; } } public class AsyncAwait : Monobehaivor { void Start () { Mover mover = new Mover(); mover.Move(transform); } }
UniTask 插件安装 https://github.com/Cysharp/UniTask
返回值
async:声明异步
await:等待执行
UniTaskVoid:返回值
await UniTask.Yield():返回空
Return :结束异步
常用等待
await UniTask.Delay(TimeSpan.FromSeconds(1):等待一秒
await UniTask.NextFrame():等待一帧
await UniTask.WaitUntil(() => isActive == false):等待委托为true执行
1 2 3 4 5 6 7 async unitask xxx ---- await xxx / xxx.forget{ await UniTask.Delay(TimeSpan.FromSeconds(1 ), ignoreTimeScale: false , cancellationToken: _cts.Token); await UniTask.NextFrame(); await UniTask.WaitUntil(() => isActive == false ); await 异步函数 a }
示例
1 using Cysharp.Threading.Tasks;
1 2 3 4 5 6 7 8 public static Func<UniTaskVoid> func;private async UniTaskVoid funtionName (){ ... await UniTask.Yield(); }
1 2 3 4 5 6 7 public static Func<UniTaskVoid> MyFunc = async () => { Debug.Log($"++++++++++++++++++++++++++执行时间:{Time.realtimeSinceStartup} " ); await UniTask.Delay(TimeSpan.FromSeconds(1.0 )); Debug.Log($"++++++++++++++++++++++++++执行时间:{Time.realtimeSinceStartup} " ); };
强制停止
由于async/await异步实现是依靠着Task实例。Task实例是有可能是多线程的,由于线程是操作系统层面的资源就导致无法直接停止一个Task。所以我们只能做一个公共变量,task在执行异步时不断检查这个变量是否改变,改变的话说明要停止执行,在Task内部自己停止。 C#提供一个“取消标记”叫做CancellationTokenSource.Token,在创建task的时候传入此参数,就可以将主线程和任务相关联,然后在任务中设置“取消信号“叫做ThrowIfCancellationRequested来等待主线程使用Cancel来通知,一旦cancel被调用。task将会抛出OperationCanceledException来中断此任务的执行,最后将当前task的Status的IsCanceled属性设为true。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var tokenSource = new CancellationTokenSource();var token = tokenSource.Token;tokenSource.Cancel(); if (token.IsCancellationRequested){ Debug.Log("Cancel" ); } token.ThrowIfCancellationRequested(); private async UniTask<string > ReadTxtAsync (string path, CancellationToken token ) { return await UniTask.Run(() => { token.ThrowIfCancellationRequested(); var str = File.ReadAllText(path); token.ThrowIfCancellationRequested(); return str; }); }
From:
https://blog.csdn.net/dzj2021/article/details/122274902?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167958917916782427425961%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=167958917916782427425961&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-122274902-null-null.blog_rank_default&utm_term=%E5%BC%82%E6%AD%A5&spm=1018.2226.3001.4450
https://blog.csdn.net/vinkey_st/article/details/126759402
https://blog.csdn.net/farcor_cn/article/details/119494954
https://www.cnblogs.com/luohengstudy/p/5623451.html