协程与进程的关系
线程是进程的执行体,拥有一个执行入口,以及从进程虚拟地址空间分配的栈信息,包括用户栈和内核栈
线程可以自己创建多个 名为协程的执行体,给他们各自指定执行入口,申请一些内存分配给他们做执行栈,那么线程就可以按需调度这几个执行体了。
为了实现这几个执行体的切换,线程也需要记录执行体的信息,包括但不仅包括:ID、栈的位置、执行入口地址、执行现场
一、用户程序不能操作内核空间,所以只能给协程分配用户栈,二、操作系统对协程一无所知,所以协程又被称为“用户态进程”
协程恢复执行时会根据之前保留的执行现场恢复到中断前的状态,继续执行,这样就通过协程实现了即轻量又灵活的由用户态调度的多任务模型
Unity协程
yield return
要实现IEnumerator接口,需要写一堆繁琐的代码,而yield return则简化了这个过程当在IEnumerator函数中写yield return时,每使用一次yield return则会往迭代器中增加一个元素内容
分帧执行的实现
在对迭代器 MoveNext时,会执行获取到的节点元素的yield return之前,上一个节点元素yield return之后的代码块,于是分帧只需要在每一帧进行yield return即可
IEnumerator e = null;
void Start()
{
e = TestCoroutine();
}
void LateUpdate()
{
if (e != null)
{
if (!e.MoveNext())
{
e = null;
}
}
}
IEnumerator TestCoroutine()
{
Log("Test 1");
yield return null; //返回内容为null
Log("Test 2");
yield return 1; //返回内容为1
Log("Test 3");
yield return "sss"; //返回内容为"sss"
Log("Test 4");
yield break; //跳出,类似普通函数中的return语句
Log("Test 5");
yield return 999; //由于break语句,该内容无法返回
}
void Log(object msg)
{
Debug.LogFormat("<color=yellow>[{0}]</color>{1}", Time.frameCount, msg.ToString());
}
延迟一定时间执行
yield return的值可以通过moveNext获取到。则可以通过yield return返回一个需要等待的类型,在外面接收到需要等待的类型时,则进行计时,计时一定时间后继续MoveNext即可
private void OnGUI()
{
if (GUILayout.Button("Test")) //注意:这里是点击触发,没有放在start里,为什么?
{
enumerator = TestCoroutine();
}
}
void LateUpdate()
{
if (enumerator != null)
{
bool isNoNeedWait = true, isMoveOver = true;
var current = enumerator.Current;
if (current is MyWaitForSeconds)
{
MyWaitForSeconds waitable = current as MyWaitForSeconds;
isNoNeedWait = waitable.IsOver(Time.deltaTime);
}
if (isNoNeedWait)
{
isMoveOver = enumerator.MoveNext();
}
if (!isMoveOver)
{
enumerator = null;
}
}
}
IEnumerator TestCoroutine()
{
Log("Test 1");
yield return null; //返回内容为null
Log("Test 2");
yield return 1; //返回内容为1
Log("Test 3");
yield return new MyWaitForSeconds(2f); //等待两秒
Log("Test 4");
}
StartCoroutine(Method())在哪写的 就会在哪第一次执行Method方法。即如果写在Start中,就会在第一帧执行前执行Method。但如果希望不在第一帧前执行第一次,可以在Method先yield return null