參考文章:html
http://blog.csdn.net/onafioo/article/details/48979939異步
http://www.cnblogs.com/zhaoqingqing/p/3750522.htmlide
http://blog.csdn.net/alexander_xfl/article/details/41577625函數
http://www.cnblogs.com/hammerc/p/4432969.html測試
表示感謝this
線程(Thread)和協程(Coroutine) spa
D.S.Qiu以爲使用協程的做用一共有兩點:1)延時(等待)一段時間執行代碼;2)等某個操做完成以後再執行後面的代碼。總結起來就是一句話:控制代碼在特定的時機執行。.net
不少初學者,都會下意識地以爲協程是異步執行的,都會以爲協程是C# 線程的替代品,是Unity不使用線程的解決方案。線程
因此首先,請你牢記:協程不是線程,也不是異步執行的。協程和 MonoBehaviour 的 Update函數同樣也是在MainThread中執行的。使用協程你不用考慮同步和鎖的問題。3d
FixedUpdate()、Update()、LateUpdate()執行順序
在瞭解協程以前,咱們先了解一下MonoBehaviour三個update函數的執行順序。
首先先看一下官方文檔:https://docs.unity3d.com/Manual/ExecutionOrder.html
其中有一張流程圖如圖1所示:
咱們能夠發現,三個update的執行順序爲:FixedUpdate()--->Update()--->LateUpdate()
這個順序不用說,固然是對的,可是我仍是專門用一個場景去測試。(由於我很閒啊,因此。。。。)
圖2
首先場景中有兩個空object,其中GameObject上掛載了兩個腳本,掛載順序是Test1.cs,Test2.cs。GameObject(1)上掛載了一個腳本,掛載順序是Test3.cs,兩個object的順序如圖2所示,腳本代碼以下:
Test1.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Test1 : MonoBehaviour { // Use this for initialization void Start () { Debug.Log("test1 Start"); } // Update is called once per frame void Update() { Debug.Log("test1 update"); } void FixedUpdate() { Debug.Log("test1 FixedUpdate"); } void LateUpdate() { Debug.Log("test1 LateUpdate"); } }
Test2.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Test2 : MonoBehaviour { // Use this for initialization void Start () { Debug.Log("test2 Start"); this.StartCoroutine(NumCoutine()); this.StartCoroutine(TimeCoutine()); this.StartCoroutine(FixedCoutine()); this.StartCoroutine(FuncCoutine()); } IEnumerator NumCoutine() { Debug.Log("num xxxxxxxxxxxxxxxxxxxxx"); yield return 1; Debug.Log("num yyyyyyyyyyyyyyyyyyyyy"); } IEnumerator TimeCoutine() { Debug.Log("time xxxxxxxxxxxxxxxxxxxxx " + System.DateTime.Now.TimeOfDay.ToString()); yield return new WaitForSeconds(0.1f); Debug.Log("time yyyyyyyyyyyyyyyyyyyyy " + System.DateTime.Now.TimeOfDay.ToString()); } IEnumerator FixedCoutine() { Debug.Log("fixed xxxxxxxxxxxxxxxxxxxxx"); yield return new WaitForFixedUpdate(); Debug.Log("fixed yyyyyyyyyyyyyyyyyyyyy"); } IEnumerator FuncCoutine() { Debug.Log("func xxxxxxxxxxxxxxxxxxxxx"); yield return StartCoroutine(Func2Coutine()); Debug.Log("func yyyyyyyyyyyyyyyyyyyyy"); } IEnumerator Func2Coutine() { Debug.Log("func222 xxxxxxxxxxxxxxxxxxxxx"); yield return null; Debug.Log("func222 yyyyyyyyyyyyyyyyyyyyy"); } // Update is called once per frame void Update () { System.Threading.Thread.CurrentThread.Join(1000); Debug.Log("test2 update"); } void FixedUpdate() { Debug.Log("test2 FixedUpdate"); } void LateUpdate() { Debug.Log("test2 LateUpdate"); } }
Test3.cs:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Test3 : MonoBehaviour { // Use this for initialization void Start() { Debug.Log("test3 Start"); } // Update is called once per frame void Update() { Debug.Log("test3 update"); } void FixedUpdate() { Debug.Log("test3 FixedUpdate"); } void LateUpdate() { Debug.Log("test3 LateUpdate"); } }
雖然上面說過了,執行順序是Start()--->FixedUpdate()--->Update()--->LateUpdate(),可是兩個不一樣的物體,其中一個物體上又有兩個腳本,那麼它又會是怎樣的執行順序呢?
先說結論:如圖2中的子物體順序,先執行第二個(最後一個)子物體GameObject(1)上腳本(Test3.cs)的Start(),接着執行第一個子物體GameObject下最後一個腳本(Test2.cs)的Start(),最後是第一個子物體GameObject下第一個腳本(Test1.cs)的Start(),其餘如update函數順序相同。即就是說Update()並非說當前腳本的update,而是全部的當前運行的MonoBehaviour腳本的Update()依次都要執行。Unity是單線程的
Update():每幀只執行一次,跟時間無關,只跟幀數有關,好的機器上間隔時間短,差的機器上間隔時間長
FixedUpdate():按照固定的時間間隔執行,時間在Edit--->Project Settings--->Time中設置,可是因爲FixedUpdate()以後還有其餘好比Update()要執行,若是Updata()中花費了較長時間,那麼FixedUpdate()也不能保證按照固定間隔執行,可是當輪到他執行時,他會進行屢次補幀,將以前時間間隔應該要執行的次數都補償執行完纔開始下面其餘函數好比Update()的執行
協程(Coroutine)
協程的五種返回值含義:
Normal coroutine updates are run after the Update function returns. A coroutine is a function that can suspend its execution (yield) until the given YieldInstruction finishes. Different uses of Coroutines:
yield 0; The coroutine will continue after all Update functions have been called on the next frame.(yield return 0,yield return 100,yield return null,yield return "hello",這幾種是相同的意思,等同與yield return 0)
yield WaitForSeconds(2); Continue after a specified time delay, after all Update functions have been called for the frame
yield WaitForFixedUpdate(); Continue after all FixedUpdate has been called on all scripts
yield WWW Continue after a WWW download has completed.
yield StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to complete first.
代碼如上面各Test.cs所列,依次來分析我測試的運行結果
圖3
首先,在圖3中,咱們能夠得出以前關於調用順序的結論。
其次,在Test2.cs中啓動了五個不一樣返回類型的協程,調用順序如上,可見協程是啓動後立刻執行的,第一次是不會等待各類Start,update等執行完成的
另外,我在其中啓動了一個返回值爲WaitForSeconds(0.1f)的協程,啓動時間爲16.50
圖4
首先,如圖4所示,返回值爲WaitForFixedUpdate()的協程會在當前幀3個腳本的FixedUpdate()都執行完成後再啓動執行
其次,各update執行順序如上所示
首先,返回值yield return 1;與yield return StartCoroutine(Func2Coutine());的協程都是會在下一幀(注意跟WaitForFixedUpdate()不一樣)的全部Update()執行完成後纔開始啓動執行
其次,。。。。。。寫順手了,沒摟住
圖5
如今咱們要討論的是WaitForSeconds(0.1f)這個協程
先看一個官方文檔:https://docs.unity3d.com/ScriptReference/WaitForSeconds.html
其中有寫到:
Suspends the coroutine execution for the given amount of seconds using scaled time.
The actual time suspended is equal to the given time multiplied by Time.timeScale.
See WaitForSecondsRealtime if you wish to wait using unscaled time.
WaitForSeconds can only be used with a yield
statement in coroutines.
Note: There are some factors which can mean the actual amount of time waited does not precisely match the amount of time specified.
- WaitForSeconds starts waiting at the end
of the current frame. So, if you start a WaitForSeconds with duration 't' in a very long frame (for example, one which has a long operation which blocks the main thread such as some synchronous loading), the coroutine will return 't' seconds after
the end of the frame, not 't' seconds after it was called.
- WaitForSeconds will allow the coroutine to resume on the first frame after 't' seconds has passed, not exactly after 't' seconds has passed.
經過測試和上述內容可知:WaitForSeconds(0.1f)中的0.1s並非說從協程開始時算0.1s,而是在該幀的全部Update()都執行完成後開始算0.1s,那麼必定是0.1s嗎,並非,我在Test2.cs中有一個執行中止主線程1s的操做,能夠發現如圖5所示,從新啓動的時間長達3s左右,因此這個0.1s只是一個估值,具體若是某個update中時間較長,他也會等待update執行完成,具體緣由,我只是有測試結果,但願有人解惑,不勝感激,Orz。。。
另外:
經過設置MonoBehaviour腳本的enabled對協程是沒有影響的,但若是 gameObject.SetActive(false) 則已經啓動的協程則徹底中止了,即便在Inspector把gameObject 激活仍是沒有繼續執行。也就說協程雖然是在MonoBehvaviour啓動的(StartCoroutine)可是協程函數的地位徹底是跟MonoBehaviour是一個層次的,不受MonoBehaviour的狀態影響,但跟MonoBehaviour腳本同樣受gameObject 控制,也應該是和MonoBehaviour腳本同樣每幀「輪詢」 yield 的條件是否知足。
我猜,協程啓動後可能引擎會複製一份代碼存儲到某個「協程專用區」,即便將enabled設置爲false也沒用,由於代碼已經拷貝好了,照樣會執行,可是若是gameobject被隱藏,那麼因爲找不到協程所屬的對象,將不會在被執行,這是我猜的,猜的不對你來打我呀。。。