一.Unity是否支持多线程?
Unity支持多线程的使用,可以使用C#的Thread类来创建和管理线程,只需要引入这个类:
但需要注意的是,在Unity中,只有主线程(也称为渲染线程)可以访问Unity对象,如GameObject、Transform等,如果在其他线程中访问这些对象,会导致不可预期的结果。
因此,在使用多线程时,需要遵循一些规则:
- 不要在非主线程中访问Unity对象;
- 不要在多个线程中同时修改同一个对象或变量,否则可能会导致竞态条件;
- 不要过度使用多线程,因为线程切换会带来额外的开销,而且多线程可能会增加代码复杂性和调试难度;
- Unity中的多线程记得使用后要关闭,否则会在推出调试后一直运行,直到关闭Unity或者改变相应的脚本代码。
示例:
新开的线程虽然不能访问Unity中的对象,但是多线程可以进行一些复杂的逻辑计算:例如A*算法、网格计算,复杂的计算可能会卡住主线程,因此我们用副线程去计算。算好了,主线程再去调用。
示例:
二.什么是协程?
协程全称协同程序,不同于多线程,它不是一个线程,依附于Unity主线程。
- 新开一个线程是独立的一个管道,和主线程并行执行;
- 新开一个协程是在原线程之上开启,进行逻辑分时分步执行;
主要作用:
协程适合处理需要分阶段执行的操作,如动画、延迟执行等。协程可以让程序在一定时间后再次执行,而不会阻塞主线程。这样可以避免线程切换带来的开销,提高程序性能。
也就是说,协程是把可能会让主线程卡顿的耗时操作分时分步进行。
主要适用场景:
异步加载文件、异步下载文件、场景异步加载、批量创建时防止卡顿。
协程可以将一个函数分成多个部分,在每个部分执行完后暂停,等待下一次唤醒继续执行。如果不唤醒,这个协程就会被挂起。这种方式可以有效地控制程序的执行流程,使得我们可以更加灵活地控制程序的运行逻辑。
三.协程的使用
首先,要想使用协程,类要继承MonoBehavior;
在Unity中,我们可以使用Coroutine类来创建协程。通常情况下,我们会将协程定义为一个函数(使用IEnumerator关键字),然后通过StartCoroutine方法启动它。
示例:
在这个例子中,我们定义了一个名为MyCoroutine的协程函数,该函数使用yield语句分别暂停1秒和2秒,并在每次暂停后输出一条调试信息。然后,在Start函数中,我们通过StartCoroutine方法启动了这个协程。
需要注意的是,协程函数必须返回IEnumerator类型,而不是void类型。协程函数中使用yield语句来暂停执行,并返回一个对象,告诉协程系统应该何时继续执行。
协程可以通过传递参数来控制它们的行为。我们可以将参数作为协程函数的参数,并在启动协程时传递给它。
示例:
在这个例子中,我们定义了一个名为MyCoroutine的协程函数,该函数接受两个参数:一个字符串和一个浮点数。然后,在Start函数中,我们通过StartCoroutine方法启动了这个协程,并将"Hello"和3分别作为参数传递给它。
需要注意的是,在协程函数内部,我们可以像普通函数一样使用传递进来的参数。但是,在协程函数之外,我们不能直接访问协程内部的变量。如果需要在协程之间共享数据,可以考虑使用静态变量或其他线程安全的机制。
不同的yield return
- yield return null:暂停协程一帧,然后继续执行下一帧, 在Update和LateUpdate之间执行;
- yield return new WaitForSeconds(float seconds):暂停协程指定时间后继续执行,在Update和LateUpdate之间执行;
- yield return new WaitForEndOfFrame():暂停协程直到当前帧渲染完毕后(摄像机和GUI)继续执行,在LateUpdate之后的渲染相关处理之后执行;
- yield return new WaitForFixedUpdate():暂停协程直到下一次FixedUpdate后继续执行,在FixUpdate和碰撞检测相关函数之后执行;
- yield return StartCoroutine(coroutine):暂停当前协程并启动一个新的协程,直到新的协程执行完成后继续执行当前协程。
- yield break:结束当前协程的执行。
四.协程受对象和组件失活销毁的影响
当我们开启了一个协程,如果
- 挂载此脚本的组件和物体销毁,协程将不会执行;
- 物体失活协程不执行;
- 组件失活协程执行。
五.协程的原理
协程的本质:
协程的本质是利用迭代器(Iterator)来实现的。在C#中,迭代器是一种特殊的对象,它可以在循环中依次返回集合中的元素。在协程中,yield return语句返回的也是一个迭代器对象,当协程执行到yield return语句时,会将执行权交给调用者,并返回一个迭代器对象,等待调用者再次调用协程来继续执行。这样就实现了协程的暂停和继续执行功能。
所以在脚本中调用协程相当于是把一个协程函数(迭代器)放入Unity的协程调度器(StartCoroutine()函数)中帮助我们管理进行执行,具体的yield return 后面的规则,也是Unity定义的一些规则。
我们通过获取IEnumerator对象手动模拟协程的管理;
定义一个协程函数,无法单独调用:
获取这个协程对象:
IEnumerator内部方法,属性如下:
执行MoveNext(),控制台会输出“test 1”,执行了一行语句;
执行Reset(),控制台会输出 “1”, 打印的是yield return 的返回值;
如果打印Current,控制台会输出yield return的参数值;
例如这种写法,Reset会输出“123 Vector3”,Current会输出“123 (1,2,3)”;
另一个问题来了,我们可以用MoveNext()和Current来一步步执行协程函数。但是如果协程函数中有n个yield return 怎么办?写n个MoveNext()和Current吗?
注意:MoveNext()返回bool值,当协程函数内还有可以执行的内容时返回true,没有返回flase。
使用while输出执行协程函数中的所有值:
总结:
协程的本质就是利用C#的迭代器函数“分步执行”的特点加上协程调度逻辑实现的一套分时执行函数的规则。