前言
在使用Unity,一般不考虑多线程,原因是在Unity中,只能在主线程中获取物体的组件、方法、对象,脱离这些,Unity的许多功能无法实现,这就失去了使用多线程的意义。
线程和协程的区别:
对于协程而言
markdown
同一时间只能执行一个协程,而线程是并发的,可以同时有多个线程运行;
线程和协程的使用上是相同的,共享堆,不共享栈
协程
协程不是线程,协程依旧是在主线程中执行,协程是通过迭代器来实现的,通过IEnumerator定义迭代方法,主要使用的是IEnumerator,而不是IEnumerable。
IEnumerator和IEnumerable的区别:
IEnumerator:是一个实现迭代器的接口。
IEnumerable:是在IEnumerator基础上的一个封装接口
协程的使用
首先通过一个迭代器定义一个返回值为IEnumerator的方法,然后再程序中通过StartCoroutine来开启一个协程即可:
在正式开始代码之前,需要了解StartCoroutine的两种重载方式:
StartCoroutine(string methodName):这种是没有参数的情况,直接通过方法名(字符串形式)来开启协程
StartCoroutine(IEnumerator routine):通过方法形式调用
StartCoroutine(string methodName,object values):带参数的通过方法名进行调用
js
Coroutine coroutine1;
IEnumerator Demo()
{
Debug.Log("Hello");
yield return new WaitForSeconds(5);
Debug.Log("World");
}
public void Test()
{
//第一种调用方式和第二种方式,通过方法名和参数调用
//coroutine1=StartCoroutine("Demo");
//第三种调用方式
coroutine1= StartCoroutine(Demo());
}
private void Start()
{
Test();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Z))
{
Debug.Log("按下了z键");
// StopCoroutine("Demo");
StopCoroutine(coroutine1);
}
}
在一个协程开始后,同样会对应一个结束协程的方法StopCoroutine与StopAllCoroutines两种方式,但是需要注意的是,两者的使用需要遵循一定的规则,在介绍规则之前,同样介绍一下关于StopCoroutine重载:
StopCoroutine(string methodName):通过方法名(字符串)来进行
StopCoroutine(IEnumerator routine):通过方法形式来调用
StopCoroutine(Coroutine routine):通过指定的协程来关闭
使用是有一定的规则的,规则是前两种结束协程方法的使用上,如果我们是使用StartCoroutine(string methodName)来开启一个协程的,那么结束协程就只能使用StopCoroutine(string methodName)和StopCoroutine(Coroutine routine)来结束协程
关于Yield
js
yield return null; 暂停协程等待下一帧继续执行
yield return 0或其他数字; 暂停协程等待下一帧继续执行
yield return new WairForSeconds(时间); 等待规定时间后继续执行
yield return StartCoroutine("协程方法名");开启一个协程(嵌套协程)
yield return GameObject; 当游戏对象被获取到之后执行 yield return new WaitForFixedUpdate():等到下一个固定帧数更新 yield return new WaitForEndOfFrame():等到所有相机画面被渲染完毕后更新 yield break; 跳出协程对应方法,其后面的代码不会被执行
协程小用法
5.1、将一个复杂程序分帧执行:
如果一个复杂的函数对于一帧的性能需求很大,我们就可以通过yield return null将步骤拆除,从而将性能压力分摊开来,最终获取一个流畅的过程,这就是一个简单的应用
举一个案例,如果某一时刻需要使用Update读取一个列表,这样一般需要一个循环去遍历列表,这样每帧的代码执行量就比较大,就可以将这样的执行放置到协程中来处理:
public class Test : MonoBehaviour { public List nums = new List { 1, 2, 3, 4, 5, 6 };
csharp
private void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(PrintNum(nums));
}
}
//通过协程分帧处理
IEnumerator PrintNum(List<int> nums)
{
foreach(int i in nums)
{
Debug.Log(i);
yield return null;
}
}
}
上面只是列举了一个小小的案例,在实际工作中会有一些很消耗性能的操作的时候,就可以通过这样的方式来进行性能消耗的分消
5.2、进行计时器工作
当然这种应用场景很少,如果我们需要计时器有很多其他更好用的方式,但是你可以了解是存在这样的操作的,要实现这样的效果,需要通过yield return new WaitForSeconds()的延时执行的功能:
csharp
IEnumerator Test()
{
Debug.Log("开始");
yield return new WaitForSeconds(3);
Debug.Log("输出开始后三秒后执行我");
}
5.3、异步加载等功能
只要一说到异步,就必定离不开协程,因为在异步加载过程中可能会影响到其他任务的进程,这个时候就需要通过协程将这些可能被影响的任务剥离出来
常见的异步操作有:
AB包资源的异步加载 Reaources资源的异步加载 场景的异步加载 WWW模块的异步请求
使用 StartCoroutine(函数()); 形式开启的,只能用接收返回值的形式去停止;【不限制参数个数】
使用 StartCoroutine("函数名"); 形式开启的,可以使用 StopCoroutine("函数名");
形式停止, 也可使用 接收返回值的形式去停止。【缺点:只可以传递一个参数】
两种开启形式均受到 StopAllCoroutines() 控制。StopAllCoroutines() 可以停止当前脚本中所有协程。
gameObject.SetActive(false); 可停掉所有此GameObject上的所有协程,且再次激活时协程不会继续。
StopCoroutine(函数()); 脚本.enabled = false; 不可停掉协程。