异步方案
逻辑放在 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