异步方案

  • 逻辑放在 Update / LateUpdate
    • 容易出错,且依赖于 Monobehavior
  • 协程:通过开启协程进行异步操作
    • 消耗大:每帧消耗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执行

原理

  • 返回值 IEnumerator 和等待时间
1
2
3
4
5
public class YieldInstruction
{
public IEnumerator ie;
public float executeTime;
}
  • Coroutine 类
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
// object 向另一个 position 移动的协程
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; // 一直执行while,执行一次返回一次null
}
print("Reach the Target!");
yield return new WaitForSeconds(3f); // 循环结束后更新执行
print("MyCoroutine is finished!"); // 3s后输出 协程结束
}
}

判空与强制停止

  • 将协程用 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

  • Thread 新建一个8秒的线程,按顺序做完
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();
}
  • Task,两个同时进行
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("做荤菜");
})
}

关键字

  • async:声明异步
  • await:延迟执行

示例

  • 命名空间
1
using system.Threading.Tasks;
  • 不依赖于 Monobehavior 的异步
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 执行后执行下一行函数
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}");
};
  • 示例

image-20230324024923105

image-20230324024129576

强制停止

  • 由于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
//生成Token
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
//将Token设成取消
tokenSource.Cancel();
//可以判断token是否取消了
if (token.IsCancellationRequested)
{
Debug.Log("Cancel");
}
token.ThrowIfCancellationRequested();//如果token是cancel的话,就抛出OperationCanceledException异常。

// 判断
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