为什么在unity中一般不考虑多线程

​ 在Unity中,使用多线程有较大的限制:

  1. UnityEngine中的API对象不能在子线程中使用
  2. UnityEngine中定义的基本数据类型可以使用(如float、int、struct)。但是texture2D不能使用,因为其父类是UnityEngine.Object

​ 多线程的常见使用场景一般只在于:

  • 网络通信
  • 需要处理复杂算法的计算
  • 复杂密集的I/O操作

线程与协程的区别

  • 对于协程而言,同一时间只能执行一个协程,而线程是并发的,可以同时有多个线程在运行
  • 两者在内存上的使用是相通的,共享堆,不共享栈

最关键的也最简单的区别在于在微观上线程是并行的,而协程是串行的,协程的本质还是单线程,依旧是在主线程中进行。

协程的原理

协程是通过迭代器来实现功能 ,通过关键字IEnumerator来定义一个迭代方法。注意这里使用的是IEnumerator,而不是IEnumerable

这二者间的区别:

  • IEnumerator:非泛型的,也是协程认可的参数
  • IEnumerable:通过泛型实现的迭代器,协程不使用该迭代器

在迭代器中,最关键的是yield的使用,这是实现协程功能的主要途径,通过该关键方法,可以使得协程的运行暂停、记录下一次的启动时间与位置等。

协程的使用

首先通过一个迭代器定义一个返回值为IEnumerator的方法,然后在程序中通过StartCoroutine来开启一个协程即可。StartCoroutine有多种重载方式:

  • StartCoroutine(string methodName):没有参数的情况,可以直接通过方法名来开启协程
  • StartCoroutine(IEnumerator routine):通过方法的形式调用
  • StartCoroutine(string methodName, object values):带参数的协程通过方法名进行调用
IEnumerator Test(int i)
{
// 代码块
yield return 0;
// 代码块
}

public void TestMethod() {
StartCoroutine("Test", 1); // 第1、2种调用方式,通过方法名和参数来调用
StartCoroutine(Test(1)); // 第3种调用方式,直接通过方法调用
}

在一个协程开始后,同样会对应一个结束协程的方法StopCoroutine与StopAllCoroutines两种方式,但是需要注意的是,两者的使用需要遵循一定的规则。StopCoroutine和StartCoroutine一样有多种重载方法:

  • StopCoroutine(string methodName):通过方法名(字符串)来进行
  • StopCoroutine(IEnumerator routine):通过方法形式来调用
  • StopCoroutine(Coroutine routine):通过指定的协程来关闭

注意:如果使用字符串作为StartCoroutine的参数,在StopCoroutine中也一样需要使用StopCoroutine(string methodName)来结束协程,其他方式同理

在unity中暂停协程(yield)

有几种不同类型的yield语句会暂停协程,在所有情况下,都需要在希望函数被中断的时候使用关键字yield return。

yield标明该方法是一个迭代器,并且将执行超过一帧,而return与常规函数一样,会在该点终止执行并将控制权交还给调用方法。

不同之处在于,使用协程,unity知道从中断的地方继续执行该方法。yield return之后的内容将指定协程在继续之前将停止多长时间。

yield return null

yield return null指示unity等到下一帧再继续

IEnumerator MyCoroutine()
{
int i = 0;
while (i < 10)
{
// Count to Ten
i++;
yield return null;
}
while (i > 0)
{
// Count back to Zero
i--;
yield return null;
}
// All done!
}

unity将完成第一个循环,每帧计数一个数字,然后开始第二个循环,每帧倒数一个,直到代码块结束

WaitForSeconds

WaitForSecondsWaitForSecondsRealtime能指定确切的等待时间,它们只能在协程中使用。

IEnumerator WaitFiveSeconds()
{
print("Start waiting");
yield return new WaitForSeconds(5);
print("5 seconds has passed");
}

​ 像这样使用waitforseconds最适合一次性的等待

​ 对于重复延迟,应该先缓存WaitForSeconds对象

WaitForSeconds delay = new WaitForSeconds(1);
Coroutine coroutine;
void Start()
{
StartCoroutine("MyCoroutine");
}
IEnumerator MyCoroutine()
{
int i= 100;
while (i>0)
{
// Do something 100 times
i--;
yield return delay;
}
// All Done!
}

Wait for Seconds Real Time执行完全相同的功能,但使用的是未缩放的时间。

Yield Return WaitUntil / WaitWhile

WaitUntil 暂停执行,直到条件满足,而WaitWhile则是等待条件不满足后再继续

​ 在使用WaitUntil WaitWhile代替while循环的好处是更为方便,可以使用lambda表达式在一行代码中检查变量条件

IEnumerator CheckFuel()
{
yield return new WaitWhile(() => fuel > 0);
print("tank is empty");
}
WaitForEndOfFrame

​ 此指令会等到unity渲染完每个camera和UI元素,然后才显示实际帧。一个典型的用途是用于截屏。

IEnumerator TakeScreeshot()
{
// Waits until the frame is ready
yield return new WaitForEndOfFrame();
CaptureScreen();
}

等待另一个协程

​ 可以让yield直到另一个由yield语句触发的协程完成执行

​ 只需要在yield return之后使用StartCoroutine方法

void Start()
{
StartCoroutine(MyCoroutine());
}
IEnumerator MyCoroutine()
{
print("Coroutine has started");
yield return StartCoroutine(MyOtherCoroutine());
print("Coroutine has ended");
}
IEnumerator MyOtherCoroutine()
{
int i = 3;
while (i>0)
{
// Do something 3 times
i--;
yield return new WaitForSeconds(1);
}
print("All Done!");
}