脚本 2 协程

旧时模样 提交于 2020-01-20 04:48:44

目的

MonoBehaviour 不支持多线程,即,所有 Unity 提供给 MonoBehvaiour 调用地接口,只能在主线程调用,当然,其它原生 C# ,以及我们写地代码,只要不调用 Unity,都是可以进行多线程调用的。

Unity 提供协程,主要是为了简化异步逻辑调用,让代码可读性更高。它并不能让我们提高效率。

协程的启动与停止

启动接口

StartCoroutine

public Coroutine StartCoroutine(IEnumerator routine);

public Coroutine StartCoroutine(string methodName, object value = null);

停止接口

StopCoroutine

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 中每帧检测下载状态。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!