深入理解Unity協程

在很多編程語言中都支持協程,例如在我們之前提到的lua中,協程是一個虛擬的線程技術。


簡述


想一想我們平時購買電腦提及的,cpu是四核八線程,其實cpu原先只能處理處理一件事,也就是說cpu默認是一個核心對應一條線程的,但是如果我們需要同時處理多個任務,而我們並沒有那麼多的線程數量。
然後前人們就提出了虛擬線程的概念,將cpu的單個線程,虛擬出多條線程,也就有了我們四核八線程,八核十六線程等的概念;在應用程序這邊也有了線程和進程的概念,在把進程再細分,虛擬化軟件的線程就得到了協程的概念。

至此你知道,協程就是對線程的再細分,是線程的再虛擬化即可。

 unity的協程


雖然說協程的概念並不新穎,但在現在大趨勢的互聯網開發領域可能很少涉及到`協程`這個詞彙。
協程就是協力去完成一件事,這很容易想到多線程的概念,例如我們進行一次網絡請求我們需要等待response之後才能下一步操作,此時我們就會用到`互斥鎖`、`線程安全`等概念。
在unity中或者說在遊戲引擎中,由於受到遊戲主循環線程的制約,所以不能確保多線程的安全性,此時在同一線程下繼續使用協程來承擔多線程的工作就顯得尤爲重要。
(unity也退出了以性能優先的ECS模式,摒棄Mono框架,實現了可多線程協助開發的開發模式)
下面我們就主要以unity的協程詳細介紹。

 unity協程示例


看這個例子:

//創建協程
private IEnumerator Cor1()
{
    yield return new WaitForSeconds(2f);
    Debug.Log("2s到了");
}
//啓動協程
StartCoroutine(Cor1());


這是一個簡單示例,可以看到協程需要返回一個`IEnumerator`可迭代對象,這本來是Csharp中的迭代器模式的實現,在unity中unity以此爲原型實現了協程。

 協程的參數


在上面我們使用了 `new WaitForSeconds()`,這表示等待指定的時間。【注意WaitForSeconds與Time.Scale相關】
在上面使用的WaitForSeconds之外還有許多的參數,這些參數要麼需要花費時間,要麼返回bool,總之就是需要確定一個moveNext。


協程的使用情況

 

1. 用於不確定的時長情況(例如:網絡請求,讀寫文件)
2. 用於延遲執行
3. 可當做簡易計時器使用(例如:生產一批敵人)

 協程的嵌套


協程支持嵌套,如下是一個利用協程實現的巡邏的簡單實現。
**注:在unity中,協程返回0或null表示等待下一幀。**

using System;
using System.Collections;
using UnityEngine;

namespace Coroutines
{
    //協程測試
    public class CoroutTest : MonoBehaviour
    {
        public Transform[] wayPoints;

        private bool isLoop;

        private void Start()
        {
            isLoop = true;
            StartCoroutine(StartLoop());
        }

        private IEnumerator StartLoop()
        {
            do
            {
                foreach (var point in wayPoints)
                {
                    yield return StartCoroutine(MoveTarget(point.position));
                }
            } while (isLoop);
        }

        private IEnumerator MoveTarget(Vector3 target)
        {
            while (transform.position!=target)
            {
                transform.position = Vector3.MoveTowards(transform.position, target, 3 * Time.deltaTime);
                yield return 0;
            }
        }
    }
}


 讓協程動起來

 

+ StartCoroutine(nameof(StartLoop));
以字符串形式啓動協程,能夠在外部停止協程,無法傳遞參數。

+ StartCoroutine(StartLoop);
以迭代器形式啓動協程,能夠傳遞參數,無法在外部使用stop停止協程。

### 讓協程停下來
協程本質是一個迭代器,當moveNext爲false時即認爲協程中所有的項目都已經執行完畢。
在unity中有以下幾種方式停止協程:

1. StopCoroutine()  注意此方式只能停止以字符串形式啓動的協程 【在協程外部使用】
2. yield break   跳出協程【在協程內部使用】
3. 通過邏輯來停止 【使其協程執行條件不滿足】
4. 設置物體不激活 【再次激活協程也不會恢復】
5. StopAllCoroutine() 終止所有協程

如上面協程嵌套的例子中,如果我們想要協程停止:
1. 設置isLoop=false;讓其在執行一次後不滿足條件自動停下
2. 在協程內部break
 

if (transform.position==wayPoints[2].position)
{
    yield break;
}


3. 在協程外部 stop
 

 StopCoroutine(nameof(StartLoop));

 協程的設計思想

 協程是否取代update?


通過上面的例子,你大可發現,協程其實是對update的另一種實現,我們甚至可以只使用協程而不使用任何update和fixedUpdate完成程序的編寫。
但我們如果這樣做不是本末倒置了嗎?協程是unity推出的延遲執行的一種範式,其還是基於update爲原理的上層實現。

使用協程會大大提升程序效率嗎?


不會,協程本質上還是在一條線程上,儘管可以多條協程並行,但這些協程始終還是運行在一條線程上,速度和效率並不會得到很大的提升。反而開闢多條線程並行,線程需要多多協程的狀態保持監聽,在協程大量結束時會觸發大量GC 回收,可能會降低程序的運行效率。

 總結


協程是運行在線程上的線程,其運作方式任然基於單線程,並不會因爲使用協程提高程序的運行效率,但協程方便的書寫方式,強大的功能能夠提高我們作爲開發者的開發效率。
從某種意義上來講,協程更像是一個精美的語法糖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章