引入
在编写如延时操作,UI和其他动画控制,加载资源(场景,模型和贴图等),等待网络请求等操作时,需要将处理操作分布多帧中或者为了不阻塞主线程需要异步编程。考虑现在可能只需要简单延时操作,UI动画控制,加载等操作,先从unity的协程coroutine和C#的Task类考虑。
unity协程
首先需要知道unity的大部分游戏逻辑,输入处理和渲染都是在主线程处理处理,是单线程的。大部分API只能在主线程调用,子线程尝试调用可能有异常和未定义错误。而unity协程本质上也是在主线程上运行的,协程函数任务被分配到不同帧中处理。
语法
协程方法通过C#的迭代器关键字IEnumerator,定义一个返回迭代器的函数实现;并在函数体通过yiled return YieldInstruction(waitForSeconds,waitForFixedUpdate等的基类)暂停协程函数和决定什么时候恢复执行;最后通过StartCoroutine(IEnumerator routine)开始协程函数。
示例代码如下:
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
|
using UnityEngine;
//协程是在MonoBehavour实现,只能在MonoBegaviour脚本中使用
public class TestExample : MonoBehaviour
{
private IEnumerator Test()
{
for(int i = 0; i < 10; i++)
{
Debug.Log(i + " loop, in " + Time.frameCount + " frame.");
yield return null;
}
}
void Start()
{
StartCoroutine(Test());
}
}
//最终输出:
//0 loop, in 1 frame
//1 loop, in 2 frame
//2 loop, in 3 frame
//...
//3 loop, in 10 frame
|
yield return null即暂停后在下一帧执行。
还可以使用:
1
2
3
4
|
yield return new WaitForSeconds(1);//等待1秒,受到TimeScale影响。
yield return new WaitForSecondsRealTime(1);//等待1秒,不受到TimeScale影响。
yield return new WaitForFixedUpdate();//等待到下一次fixedUpdate执行。
yield return StartCoroutine(anoTher Coroutine);//等待另一个协程执行完成后。
|
局限
使用范围
协程只能在继承了MonoBehaviour的类在启动,依赖于组件类型脚本和场景中物体或者空物体。
生命周期问题
协程和挂载脚本的GamwObject绑定,对象销毁,协程停止而不会销毁。可能引发内存泄漏,空引用或者未知错误。
性能问题
yield return 时候频繁创建返回对象造成GC。
由于实际是在主线程上,过多协程还是会造成阻塞。
如果涉及到数据读取,yield return null不好把握一帧读取多少。因为单线程,一帧读取多会阻塞,少了浪费时间。
功能实现局限
协程函数无法返回值
协程函数不能使用try,catch进行错误捕获。
c# Task类
c# Task类是.Net框架下提供异步操作的核心类,基于Thread,ThreadPool的高度抽象。能够以直观,高效的方式编写异步,多线程代码,而不需要关心线程的生命周期,如创建,启动,暂停和恢复等。
基本用法
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
|
using System.Threading.Tasks;
private async Task<string> TestAsync()
{
Debug.Log("async Task start!");
await Task.Delay(3000);
return "Task completed! return result";
}
private async void Start()
{
string res = await TestAsync();
Debug.log("res");
}
private void Update()
{
Debug.log("Update!");
}
//预期输出:
//async Task start
//Update!
//Update:
//.....
//after 3s
//Task completed! return result
|
async关键字允许函数进行异步操作,即可以在函数中使用await关键字。当遇到需要等待另一个任务完成,使用await关键字会暂停函数将控制权返回到调用方,等待任务完成后再回到该异步函数。
Task常用函数
1
2
3
4
5
6
|
using System.Thread.Tasks;
Task.Run(Action action);//委托或者lambda函数表达式。
Task.Delay(Int time);//创建在time秒后结束的任务。
Task.WhenAll(Task[] tasks)//等待数组中所有任务完成。
Task.WhenAny(Task[] tasks)//等待数组中任一任务完成。
|
除此以外还有其他高级用法,现阶段可能还用不上,等有更高级的需求在补充。