目的
MonoBehaviour 不支持多线程,即,所有 Unity 提供给 MonoBehvaiour 调用地接口,只能在主线程调用,当然,其它原生 C# ,以及我们写地代码,只要不调用 Unity,都是可以进行多线程调用的。
Unity 提供协程,主要是为了简化异步逻辑调用,让代码可读性更高。它并不能让我们提高效率。
协程的启动与停止
启动接口
public Coroutine StartCoroutine(IEnumerator routine);
public Coroutine StartCoroutine(string methodName, object value = null);
停止接口
public void StopCoroutine(string methodName);
public void StopCoroutine(IEnumerator routine);
public void StopCoroutine(Coroutine routine);
public void StopAllCoroutines(); // 停止所有协程
例子
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour
{
private IEnumerator coroutine;
void Start()
{
print("Starting " + Time.time);
// 方法1:Coroutine StartCoroutine(IEnumerator routine);
Enumerator coroutine = WaitAndPrint(2.0f);
// 启动
StartCoroutine(coroutine);
// 停止
StopCoroutine(coroutine);
// 方法2:
// 启动
Coroutine coroutine = StartCoroutine(WaitAndPrint(2.0f));
// 停止
StopCoroutine(coroutine);
// 方法3:Coroutine StartCoroutine(string methodName, object param)
// 启动
StartCoroutine("WaitAndPrint", 2.0f);
// 停止
StopCoroutine("WaitAndPrint");
}
// every 2 seconds perform the print()
private IEnumerator WaitAndPrint(float waitTime)
{
while (true)
{
// IEnumerator.MoveNext() == true
// IEnumerator.Current == new WaitForSeconds(waitTime)
yield return new WaitForSeconds(waitTime);
print("WaitAndPrint " + Time.time);
}
// IEnumerator.MoveNext() == false
}
}
yield return
Unity 提供了多种方式来 yield 来适应不同场景
- 协程函数执行完最后一行,没有 yield return ,完成协程
- yield break: 直接从当前协程退出
- WaitForFixedUpdate() : 等待下次 FixUpdate后执行
- null: 等待下一帧继续执行,Update 后,LateUpdate 前
- WaitForSecnodsRealtime(n) : 等待n秒后执行,不带Time.timeScale
- WaitForSecnods(seconds) : 等待second秒,Update 后,LateUpdate 前
- WaitUntil(expression) : 直到表达式计算结果为true,结束
- WaitWhile(expression) : 直到表达式结果为false,结束
- AsyncOperation : 知道异步操作的 isDone 为 true,结束,比如UnityWebRequest.SendWebRequest()的返回值,就是 AsyncOperation
- WaitForEndOfFrame:在 LateUpdate 后执行,这是可以捕捉屏幕截图
- CustomYieldInstruction: 自定义指令
- 派生 CustomYieldInstruction,重载 public override bool keepWaiting ,返回 false 结束协程
- 直接派生 IEnumerator ,实现 MoveNext,返回 false 结束协程
需要注意的
- 协程的 yield return,相当多的情况下是需要 new 的,因此GC是难以避免的,所以对于有效率需求的代码,不要用协程。
- SetActive: 当 GameObject.SetActive(false)后,该对象启动的协程也会全部停止,并且当再次SetActive(true)后,也不会重启。
- 设置脚本的 enabled ,不会停止协程的执行,因为协程是属于 GameObject 的。
- 协程多数情况下,比较适用于各种异步操作,比如http请求,读写文件等。
原理
协程定义,要求返回值为 IEnumerator:
public interface IEnumerator
{
bool MoveNext();
void Reset();
Object Current{get;}
}
所以,其实 yield return ,在Mono底层,被翻译成实现 IEnumerator 的类的多个 Object。
yield break,及函数最后,IEnumerator.MoveNext() == false
可以参考CustomYieldInstruction,自定义指令,就是我们自己手动实现IEnumerator。
每次当携程执行时:
- 首先执行MoveNext(),如果为false,则该协程执行完毕
- 返回Current Object,并根据 Object类型执行操作
协程下载例子
协程不会提高效率,它的主要作用,是让代码流程更简洁,比如,我们要下载一个文件,完成后再执行一些操作
例子:
public static void Download(System.Action finishCB)
{
string url = "https: xxxx";
StartCoroutine(DownloadFile(url));
}
private static IEnumerator DownloadFile(string url)
{
UnityWebRequest request = UnityWebRequest.Get(url);
request.timeout = 10;
yield return request.SendWebRequest();
if(request.error != null)
{
Debug.LogErrorFormat("加载出错: {0}, url is: {1}", request.error, url);
request.Dispose();
yield break;
}
if(request.isDone)
{
string path = "xxxxx";
File.WriteAllBytes(path, request.downloadHandler.data);
request.Dispose();
yiled break;
}
}
如果不借助协程,我们需要启动下载,并在 Update 中每帧检测下载状态。
来源:CSDN
作者:lrh3025
链接:https://blog.csdn.net/lrh3025/article/details/103794562