[Unity] in depth analysis of the principle of Unity Coroutine

Respect the work of others and support originality. Please indicate the source for Reprint: http dsqiu. iteye. com

I remember when I first started my internship in June last year, I was asked to write the structure of the network layer and used the cooperation process. At that time, I was a little confused. I didn't know what the execution mechanism of the Unity cooperation process was. I just knew that the return value of the function was ienumeror type. If I used yield return in the function, I could call it through StartCoroutine. Later, it has been used in a muddle. google on the Internet is basically an example, which can rarely help to deeply understand the principle of Unity synergy.

This article only analyzes and understands the internal operation principle of the collaborative process from the perspective of Unity, rather than from the C# underlying syntax implementation (which needs to be introduced later). It is divided into three parts:

Thread and Coroutine

Execution principle of synergy in Unity

IEnumerator & Coroutine

Previously, I wrote an article "coroutene management class - TaskManager tool sharing", which mainly introduces the state control of the collaboration process implemented by TaskManager, and makes an in-depth study on the principle of the collaboration process without the background implementation of Unity. Although I had a little understanding of the collaborative process before, I still had a blank about how Unity executes the collaborative process. In unitygems Com. When I saw the Hijack class behind the Advanced Coroutine, I immediately felt very exquisite. As soon as I saw it, I moved to write and share it.

Thread and Coroutine

D.S.Qiu thinks that there are two functions of using a collaborative process: 1) delaying (waiting) a period of time to execute the code; 2) Wait for an operation to complete before executing the following code. To sum up, it is a sentence: the control code is executed at a specific time.

Many beginners will subconsciously think that the collaborative process is executed asynchronously. They will think that the collaborative process is a substitute for C# thread and a solution for Unity not to use thread.

So first of all, please keep in mind that a coroutine is not a thread, nor is it executed asynchronously. Like the Update function of monobehavior, the coroutine is also executed in MainThread. You don't have to think about synchronization and locking when using coprocessing.

Execution principle of synergy in Unity

UnityGems.com gives the definition of collaborative process:

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

That is, a coroutine is a partial execution. It will hang when it encounters a condition (yield return statement). It will not be awakened until the condition is met and continue to execute the following code.

Unity handles the co processes on the object at every Frame. Unity mainly processes the collaboration process after Update (check whether the conditions of the collaboration process are met), but there are also special cases:


From the analysis of the above figure, it can be seen that the co process is the same as Update(), which is the function (if any) that Unity will process each frame pair. If monobehavior is active and the condition of yield is met, the code behind the coprocessing method will be. It can also be found that if a coroutine is called in the early stage of an object, the coroutine will immediately run to the first yield return statement. If yield return is null, it will be awakened again in the same frame. If this detail is not considered, some strange problems "1" will appear.

『1』 The notes and conclusions are from unitygems Com. After the following verification, it is found that it is inconsistent with the actual situation. D.S.Qiu uses Unity 4.3.4f1 for testing. After testing and verification, the collaborative process is run at least after LateUpdate() of each frame.

using UnityEngine;  
using System.Collections;  
  
public class TestCoroutine : MonoBehaviour {  
  
    private bool isStartCall = false;  //Makesure Update() and LateUpdate() Log only once  
    private bool isUpdateCall = false;  
    private bool isLateUpdateCall = false;  
    // Use this for initialization  
    void Start () {  
        if (!isStartCall)  
        {  
            Debug.Log("Start Call Begin");  
            StartCoroutine(StartCoutine());  
            Debug.Log("Start Call End");  
            isStartCall = true;  
        }  
      
    }  
    IEnumerator StartCoutine()  
    {  
          
        Debug.Log("This is Start Coroutine Call Before");  
        yield return new WaitForSeconds(1f);  
        Debug.Log("This is Start Coroutine Call After");  
             
    }  
    // Update is called once per frame  
    void Update () {  
        if (!isUpdateCall)  
        {  
            Debug.Log("Update Call Begin");  
            StartCoroutine(UpdateCoutine());  
            Debug.Log("Update Call End");  
            isUpdateCall = true;  
        }  
    }  
    IEnumerator UpdateCoutine()  
    {  
        Debug.Log("This is Update Coroutine Call Before");  
        yield return new WaitForSeconds(1f);  
        Debug.Log("This is Update Coroutine Call After");  
    }  
    void LateUpdate()  
    {  
        if (!isLateUpdateCall)  
        {  
            Debug.Log("LateUpdate Call Begin");  
            StartCoroutine(LateCoutine());  
            Debug.Log("LateUpdate Call End");  
            isLateUpdateCall = true;  
        }  
    }  
    IEnumerator LateCoutine()  
    {  
        Debug.Log("This is Late Coroutine Call Before");  
        yield return new WaitForSeconds(1f);  
        Debug.Log("This is Late Coroutine Call After");  
    }  
}  

The log input results are as follows:

Then return yield return new WaitForSeconds(1f); Change to yield return null; It is found that the log input result is the same as the above, and there is no situation mentioned above:

using UnityEngine;  
using System.Collections;  
  
public class TestCoroutine : MonoBehaviour {  
  
    private bool isStartCall = false;  //Makesure Update() and LateUpdate() Log only once  
    private bool isUpdateCall = false;  
    private bool isLateUpdateCall = false;  
    // Use this for initialization  
    void Start () {  
        if (!isStartCall)  
        {  
            Debug.Log("Start Call Begin");  
            StartCoroutine(StartCoutine());  
            Debug.Log("Start Call End");  
            isStartCall = true;  
        }  
      
    }  
    IEnumerator StartCoutine()  
    {  
          
        Debug.Log("This is Start Coroutine Call Before");  
        yield return null;  
        Debug.Log("This is Start Coroutine Call After");  
             
    }  
    // Update is called once per frame  
    void Update () {  
        if (!isUpdateCall)  
        {  
            Debug.Log("Update Call Begin");  
            StartCoroutine(UpdateCoutine());  
            Debug.Log("Update Call End");  
            isUpdateCall = true;  
        }  
    }  
    IEnumerator UpdateCoutine()  
    {  
        Debug.Log("This is Update Coroutine Call Before");  
        yield return null;  
        Debug.Log("This is Update Coroutine Call After");  
    }  
    void LateUpdate()  
    {  
        if (!isLateUpdateCall)  
        {  
            Debug.Log("LateUpdate Call Begin");  
            StartCoroutine(LateCoutine());  
            Debug.Log("LateUpdate Call End");  
            isLateUpdateCall = true;  
        }  
    }  
    IEnumerator LateCoutine()  
    {  
        Debug.Log("This is Late Coroutine Call Before");  
        yield return null;  
        Debug.Log("This is Late Coroutine Call After");  
    }  
}  

"Today, I accidentally found the function execution sequence diagram of Monobehaviour, and found that the operation of the collaborative process is indeed after LateUpdate. Attached below:"

When introducing the TaskManager tool earlier, it was said that monobehavior does not provide a Stop method for a specific collaboration process. In fact, it is not. You can use monobehavior enabled = false or GameObject If active = false, you can Stop the execution of "2" of the collaborative process.

After verification, the conclusion of "2" is also wrong. The correct conclusion is mono behavior Enabled = false the coroutine will run as usual, but gameObject After setactive (false), the collaborative process stops completely. Even if the Inspector activates the gameObject, it still does not continue to execute:

using UnityEngine;  
using System.Collections;  
  
public class TestCoroutine : MonoBehaviour {  
  
    private bool isStartCall = false;  //Makesure Update() and LateUpdate() Log only once  
    private bool isUpdateCall = false;  
    private bool isLateUpdateCall = false;  
    // Use this for initialization  
    void Start () {  
        if (!isStartCall)  
        {  
            Debug.Log("Start Call Begin");  
            StartCoroutine(StartCoutine());  
            Debug.Log("Start Call End");  
            isStartCall = true;  
        }  
      
    }  
    IEnumerator StartCoutine()  
    {  
          
        Debug.Log("This is Start Coroutine Call Before");  
        yield return new WaitForSeconds(1f);  
        Debug.Log("This is Start Coroutine Call After");  
             
    }  
    // Update is called once per frame  
    void Update () {  
        if (!isUpdateCall)  
        {  
            Debug.Log("Update Call Begin");  
            StartCoroutine(UpdateCoutine());  
            Debug.Log("Update Call End");  
            isUpdateCall = true;  
            this.enabled = false;  
            //this.gameObject.SetActive(false);  
        }  
    }  
    IEnumerator UpdateCoutine()  
    {  
        Debug.Log("This is Update Coroutine Call Before");  
        yield return new WaitForSeconds(1f);  
        Debug.Log("This is Update Coroutine Call After");  
        yield return new WaitForSeconds(1f);  
        Debug.Log("This is Update Coroutine Call Second");  
    }  
    void LateUpdate()  
    {  
        if (!isLateUpdateCall)  
        {  
            Debug.Log("LateUpdate Call Begin");  
            StartCoroutine(LateCoutine());  
            Debug.Log("LateUpdate Call End");  
            isLateUpdateCall = true;  
  
        }  
    }  
    IEnumerator LateCoutine()  
    {  
        Debug.Log("This is Late Coroutine Call Before");  
        yield return null;  
        Debug.Log("This is Late Coroutine Call After");  
    }  
}  

Call this enabled = false; Results obtained:

Then put this enabled = false; Note out and replace with this gameObject. SetActive(false); The results are as follows:

It is concluded that setting the enabled of monobehavior script has no impact on the collaborative process, but if gameObject Setactive (false), the started collaboration is completely stopped. Even if the Inspector activates the gameObject, it still does not continue to execute. In other words, although the coroutine is started in monobehavior, the status of the coroutine function is completely at the same level as that of monobehavior, which is not affected by the state of monobehavior, but it is controlled by gameObject as that of monobehavior script. It should also be whether the conditions of "polling" yield per frame are met as that of monobehavior script.

The following expressions can be found after yield:

a) null - the coroutine executes the next time that it is eligible

b) WaitForEndOfFrame - the coroutine executes on the frame, after all of the rendering and GUI is complete

c) WaitForFixedUpdate - causes this coroutine to execute at the next physics step, after all physics is calculated

d) WaitForSeconds - causes the coroutine not to execute for a given game time period

e) WWW - waits for a web request to complete (resumes as if WaitForSeconds or null)

f) Another coroutine - in which case the new coroutine will run to completion before the yielder is resumed

It is worth noting that WaitForSeconds() is restricted by time Timescale effect, when time When timescale = 0f, yield return new WaitForSecond(x) will not be satisfied.

IEnumerator & Coroutine

A collaboration process is actually an ienumeror (iterator). The ienumeror interface has two methods Current and MoveNext (). The TaskManager described above uses the two methods to manage the collaboration process. You can access Current only when MoveNext() returns true, otherwise an error will be reported. When the iterator method runs to the yield return statement, it returns an expression expression and retains its Current position in the code. Execution restarts from this location the next time the iterator function is called.

Unity's work in each frame is to call the coprocessor (iterator) MoveNext() method. If it returns true, it will continue to execute from the current position.

Hijack

     Here we introduce a cross calling class of coroutines Hijack: 
using System;  
using System.Collections.Generic;  
using System.Linq;  
using UnityEngine;  
using System.Collections;  
   
[RequireComponent(typeof(GUIText))]  
public class Hijack : MonoBehaviour {  
   
    //This will hold the counting up coroutine  
    IEnumerator _countUp;  
    //This will hold the counting down coroutine  
    IEnumerator _countDown;  
    //This is the coroutine we are currently  
    //hijacking  
    IEnumerator _current;  
   
    //A value that will be updated by the coroutine  
    //that is currently running  
    int value = 0;  
   
    void Start()  
    {  
        //Create our count up coroutine  
        _countUp = CountUp();  
        //Create our count down coroutine  
        _countDown = CountDown();  
        //Start our own coroutine for the hijack  
        StartCoroutine(DoHijack());  
    }  
   
    void Update()  
    {  
        //Show the current value on the screen  
        guiText.text = value.ToString();  
    }  
   
    void OnGUI()  
    {  
        //Switch between the different functions  
        if(GUILayout.Button("Switch functions"))  
        {  
            if(_current == _countUp)  
                _current = _countDown;  
            else  
                _current = _countUp;  
        }  
    }  
   
    IEnumerator DoHijack()  
    {  
        while(true)  
        {  
            //Check if we have a current coroutine and MoveNext on it if we do  
            if(_current != null && _current.MoveNext())  
            {  
                //Return whatever the coroutine yielded, so we will yield the  
                //same thing  
                yield return _current.Current;  
            }  
            else  
                //Otherwise wait for the next frame  
                yield return null;  
        }  
    }  
   
    IEnumerator CountUp()  
    {  
        //We have a local increment so the routines  
        //get independently faster depending on how  
        //long they have been active  
        float increment = 0;  
        while(true)  
        {  
            //Exit if the Q button is pressed  
            if(Input.GetKey(KeyCode.Q))  
                break;  
            increment+=Time.deltaTime;  
            value += Mathf.RoundToInt(increment);  
            yield return null;  
        }  
    }  
   
    IEnumerator CountDown()  
    {  
        float increment = 0f;  
        while(true)  
        {  
            if(Input.GetKey(KeyCode.Q))  
                break;  
            increment+=Time.deltaTime;  
            value -= Mathf.RoundToInt(increment);  
            //This coroutine returns a yield instruction  
            yield return new WaitForSeconds(0.1f);  
        }  
    }  
   
}  

The above code implementation is that two coprocessors are called alternately, which is too subtle for such requirements.

Summary:

Take a closer look at unitygems today Com two articles about Coroutine. Although the first one (reference ①) has many errors in the verification results, it is still good for understanding the collaborative process. Especially when I found the script of Hijack, I can't wait to share it with you.

I didn't think there would be unitygems There will be mistakes in the articles on. Com. After unintentional testing, it is still found that there are great differences. Of course, this does not mean that the original author speculated without verification. D.S.Qiu thinks it is likely that the internal implementation mechanism of Unity has changed, and this kind of thing can be changed. Although Unity has been developed for many years, there are still many holes in the actual development, and he feels that Unity is weak and easy to use, But the skill of filling the pit is also essential.

It seems that many conclusions still need to pass their own verification. It's difficult to get true knowledge by copying and pasting rashly. Remember!

For reprint, please indicate the source at the beginning of the text: http://dsqiu.iteye.com/blog/2029701

Posted by ephmynus on Fri, 06 May 2022 10:19:44 +0300