协程的简单用法
简述
- 协程实际上就是一个迭代器(IEnumerator),内部可以定义一些Yield Return挂起函数,判断协程函数内部执行的逻辑(通过Yield Return 延时,跳帧,等待下载或加载等操作将函数进行分步操作)
- 协程是基于生命周期的(下面会将执行顺序),是同步的,不是异步的(同一时间只会执行一个协程)
- 协程函数会被编译成一个状态机,每个状态都会关联下一个状态直至下一个为空
函数
- 按常用排序
- WaitForSeconds
- WaitForEndOfFrame
- WaitForFixedUpdate
- WaitForSecondsRealtime
- WaitUnil
- WaitWhile
//WaitUntil:当返回值为True时则继续执行后面的内容
StartCoroutine(WaitUntilFunc());
//WaitForSeconds:延迟几秒后执行,时间单位为Float
StartCoroutine(WaitForSecondsFunc());
//WaitForSeconds:延迟几秒后执行(不受缩放影响),时间单位为Float
StartCoroutine(WaitForSecondsRealtimeFunc());
//WaitWhile:当返回值为False时则继续执行后面的内容
StartCoroutine(WaitWhileFunc());
//WaitForEndOfFrame:当前帧结束前调用
StartCoroutine(WaitForEndOfFrameFunc());
//当开启的协程结束时调用
StartCoroutine(WaitForStartCoroutine());
IEnumerator WaitUntilFunc()
{
Debug.Log("WaitUntilFunc Start~");
//10帧后执行
yield return new WaitUntil(() => _frame> 10);
Debug.Log("WaitUntilFunc End~");
}
IEnumerator WaitForSecondsFunc()
{
Debug.Log("WaitForSecondsFunc Start~");
//延迟两秒后执行(时间受缩放影响)
yield return new WaitForSeconds(2f);
Debug.Log("WaitForSecondsFunc End~");
}
IEnumerator WaitForSecondsRealtimeFunc()
{
Debug.Log("WaitForSecondsRealtimeFuncStart~");
//延迟两秒后执行(时间不受缩放影响)
yield return new WaitForSecondsRealtime(2f);
Debug.Log("WaitForSecondsRealtimeFuncEnd~");
}
IEnumerator WaitWhileFunc()
{
Debug.Log("WaitWhileFunc Start~");
//10帧后执行
yield return new WaitWhile(()=> !(_frame>10));
Debug.Log("WaitWhileFunc End~");
}
IEnumerator WaitForEndOfFrameFunc()
{
Debug.Log("WaitForEndOfFrameFunc Start~");
//当前帧结束前后调用
yield return new WaitForEndOfFrame();
Debug.Log("WaitForEndOfFrameFunc End~");
}
IEnumerator WaitForAsyncOperation()
{
Debug.Log("WaitForAsyncOperation Start~");
//当场景加载完毕后调用
AsyncOperation async = null;
//异步加载完场景时完后调用
async = SceneManager.LoadSceneAsync("SampleScene");
//异步加载完AB时调用
async = AssetBundle.LoadFromFileAsync("Path");
//异步加载完资源时调用
async = Resources.LoadAsync("Path");
//异步下载完后执行
var request = UnityWebRequest.Get(new Uri("www.baidu.com"));
async = request.SendWebRequest();
yield return async;
Debug.Log("WaitForAsyncOperation End~");
}
IEnumerator WaitForStartCoroutine()
{
Debug.Log("WaitForStartCoroutine Start~");
//当开启的协程结束时调用(不是被挂起时噢,而是完成了协程)
yield return StartCoroutine(WaitForSecondsFunc());
Debug.Log("WaitForStartCoroutine End~");
}
协程的执行顺序
官网更加详细点这里哟
执行顺序
- FixedUpdate
- yield WaitForFixedUpdate
- Update
- yield null
- yield WaitForSeconds
- yield WWW
- yield StartCoroutine
- LateUpdate
- yield WaitForEndOfFrame
协程替我们做了什么
Yleid Return
简述
yield return 用来判断协程内部是否挂起或继续执行的语句
支持类型
- IEnumerator: 通过MoveNext函数来判断是否继续挂起函数
- CustomYieldInstruction:内部通过判断keepWaiting的值来判断是否继续执行协程,true为挂起,false为继续执行
- YieldInstruction:Coroutine就是继承该对象,当yield return Coroutine时就是等待开启的协程执行结束再往后执行
- AsyncOperation:等待加载完毕再往后执行
- Coroutine:开启协程的返回值,仅仅引来引用协程,不具有任何暴露的属性和函数(当yield return coroutine时,当Coroutine为Null时协程继续往后执行)
yield return做了什么操作
可以看到yield return 修改了 current 与 state的值。而current 表示当前迭代对象的值,而state在执行代码前会被修改一次(表示正在执行),而代码执行完毕后会被修改一次(表示下一个迭代器的标识)
执行前修改了 state值(表示正在执行)
IL_0025: stfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
// [95 5 - 95 6]
IL_002a: nop
// [96 9 - 96 51]
IL_002b: ldstr "WaitForEndOfFrameFunc Start~"
IL_0030: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
IL_0035: nop
// [98 9 - 98 46]
IL_0036: ldarg.0 // this
IL_0037: newobj instance void [UnityEngine.CoreModule]UnityEngine.WaitForEndOfFrame::.ctor()
修改了当前current对象
IL_003c: stfld object TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>2__current'
IL_0041: ldarg.0 // this
IL_0042: ldc.i4.1
执行完 debug.log 后再次修改state值表示结束以及能继续迭代下一个对象
IL_0043: stfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
如何进行跳帧,延迟,等待的操作
从IL的角度分析
当协程内部只定义了一个yield return挂起函数时,不会直接编译成switch,而是成编译成类似if else语句,而当出现两个yield return或以上时就会编译成一个switch语句将代码分割。
IL语言
--C#--
IEnumerator WaitForEndOfFrameFunc()
{
Debug.Log("WaitForEndOfFrameFunc Start~");
//当前帧结束前后调用
yield return new WaitForEndOfFrame();
Debug.Log("WaitForEndOfFrameFunc End~");
yield return new WaitForEndOfFrame();
Debug.Log("WaitForEndOfFrameFunc End1~");
}
--IL部分--
.method private final hidebysig virtual newslot instance bool
MoveNext() cil managed
{
.override method instance bool [netstandard]System.Collections.IEnumerator::MoveNext()
.maxstack 2
.locals init (
[0] int32 V_0
)
IL_0000: ldarg.0 // this
IL_0001: ldfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
IL_0006: stloc.0 // V_0
IL_0007: ldloc.0 // V_0
IL_0008: switch (IL_001b, IL_001d, IL_001f)
IL_0019: br.s IL_0021
IL_001b: br.s IL_0023
IL_001d: br.s IL_004a
IL_001f: br.s IL_0070
IL_0021: ldc.i4.0
IL_0022: ret
IL_0023: ldarg.0 // this
IL_0024: ldc.i4.m1
IL_0025: stfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
// [95 5 - 95 6]
IL_002a: nop
// [96 9 - 96 51]
IL_002b: ldstr "WaitForEndOfFrameFunc Start~"
IL_0030: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
IL_0035: nop
// [98 9 - 98 46]
IL_0036: ldarg.0 // this
IL_0037: newobj instance void [UnityEngine.CoreModule]UnityEngine.WaitForEndOfFrame::.ctor()
IL_003c: stfld object TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>2__current'
IL_0041: ldarg.0 // this
IL_0042: ldc.i4.1
IL_0043: stfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
IL_0048: ldc.i4.1
IL_0049: ret
IL_004a: ldarg.0 // this
IL_004b: ldc.i4.m1
IL_004c: stfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
// [99 9 - 99 49]
IL_0051: ldstr "WaitForEndOfFrameFunc End~"
IL_0056: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
IL_005b: nop
// [100 9 - 100 46]
IL_005c: ldarg.0 // this
IL_005d: newobj instance void [UnityEngine.CoreModule]UnityEngine.WaitForEndOfFrame::.ctor()
IL_0062: stfld object TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>2__current'
IL_0067: ldarg.0 // this
IL_0068: ldc.i4.2
IL_0069: stfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
IL_006e: ldc.i4.1
IL_006f: ret
IL_0070: ldarg.0 // this
IL_0071: ldc.i4.m1
IL_0072: stfld int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
// [101 9 - 101 50]
IL_0077: ldstr "WaitForEndOfFrameFunc End1~"
IL_007c: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
IL_0081: nop
// [102 5 - 102 6]
IL_0082: ldc.i4.0
IL_0083: ret
} // end of method '<WaitForEndOfFrameFunc>d__12'::MoveNext
总结
这篇文章很好的分析了IL和C#部分
这篇文章很好的解释了跳帧,延迟,等待的操作
看IL后发现,实际上协程函数最终会被MoveNext函数调用。在switch开始之前先去取 “<>1__state” 的值,然后再去进行switch进行判断,当switch找到对应的case时并执行完了代码块就会修改 “<>1__state” 的值以及 “<>2__current”,然后结束语句,直到Unity生命周期再次调用该协程,并继续进行switch直至 “<>1__state” 的值代表为null。
根据上述文章的分析,跳帧与延迟等操作应该是在MoveNext函数内部做了特殊操作,当Current对象为延迟或跳帧对象时,会进行额外的处理
自定义一个迭代器
就不一一展示用法了,代码太长,需要自己去尝试
private void Start()
{
StartCoroutine(TestCor());
}
IEnumerator TestCor()
{
Debug.Log("TestCor Start~");
yield return new CustomIEnumerator();
yield return new Custom2IEnumerator();
yield return new Custom3IEnumerator();
yield return new Custom4IEnumerator();
Debug.Log("TestCor End~");
}
}
//继承了这四个对象则可以被yield reutrn 这样可以自定义一些挂起函数
public class CustomIEnumerator : IEnumerator
{
private int _index = -1;
private string[] strName = {"Alan", "Kino", "Bruce"};
/// <summary>
/// 每帧被调用
/// </summary>
/// <returns></returns>
public bool MoveNext()
{
_index++;
Debug.Log("MoveNext:" + (_index > strName.Length).ToString());
return false;
return _index < strName.Length;
}
public void Reset()
{
Debug.Log("Reset");
_index = 0;
}
public object Current {
get
{
Debug.Log("获取当前对象:" + strName[_index]);
return strName[_index];
}
}
}
public class Custom2IEnumerator : CustomYieldInstruction
{
public override bool keepWaiting { get; }
}
public class Custom3IEnumerator : YieldInstruction
{
}
public class Custom4IEnumerator : AsyncOperation
{
}
优化
- 有些不需要每帧进行执行的任务可以使用协程延迟
官网案例
IEnumerator DoCheck()
{
//循环调用
for(;;)
{
ProximityCheck();
//延迟0.1s后执行
yield return new WaitForSeconds(.1f);
}
}
- 缓存挂机函数
private WaitForSeconds delay = new WaitForSeconds(.1f);
IEnumerator TestCor()
{
//循环调用
for(;;)
{
//延迟0.1s后执行
yield return delay;
}
}
最后
简而言之,越深入学越知道自己的浅薄,希望各位同学能从本章中学到什么。也希望各位同学给予一些更好的观点~