Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Getting return value of a Coroutine

Discussion in 'Scripting' started by Thor-Apps, Feb 28, 2020.

  1. Thor-Apps

    Thor-Apps

    Joined:
    Dec 13, 2013
    Posts:
    32
    Hi,
    I'll like to receive the value of a couroutine.
    Here is what I did...

    Code (CSharp):
    1. public class CoroutineWithData
    2. {
    3.     public Coroutine coroutine { get; private set; }
    4.     public object result;
    5.     private IEnumerator target;
    6.     public CoroutineWithData(MonoBehaviour owner, IEnumerator target)
    7.     {
    8.         this.target = target;
    9.         this.coroutine = owner.StartCoroutine(Run());
    10.     }
    11.  
    12.     private IEnumerator Run()
    13.     {
    14.         while (target.MoveNext())
    15.         {
    16.             result = target.Current;
    17.             yield return result;
    18.         }
    19.     }
    20. }
    Code (CSharp):
    1. public class DBConnector : MonoBehaviour
    2. {
    3.  
    4.     string BASE_URL = "TO_MY_SITE";
    5.  
    6.     public DBConnector()
    7.     {
    8.     }
    9.  
    10.     public IEnumerator registerUser(User user)
    11.     {
    12.         Debug.Log("NEVER GOES HERE");
    13.  
    14.         CoroutineWithData cd = new CoroutineWithData(this, RegisterUser(user));
    15.         yield return cd.result;
    16.         Debug.Log("result is " + cd.result);  //  'success' or 'fail'
    17.  
    18.         //yield return cd.result;
    19.  
    20.        // StartCoroutine(RegisterUser(user));
    21.     }
    22.  
    23.     IEnumerator RegisterUser(User user)
    24.     {
    25.         Debug.Log("a register user");
    26.  
    27.         string action = "insertUser";
    28.         WWWForm form = new WWWForm();
    29.  
    30.         form.AddField("action", action);
    31.  
    32.  
    33.         using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
    34.         {
    35.             yield return www.SendWebRequest();
    36.  
    37.             if (www.isNetworkError || www.isHttpError)
    38.             {
    39.                 Debug.Log(www.error);
    40.                 yield return 1;
    41.             }
    42.             else
    43.             {
    44.                 Debug.Log(www.downloadHandler.text);
    45.  
    46.                 yield return 0;
    47.             }
    48.         }
    49.     }
    50. }
    And the I call the DBConnector from a Button action method...

    Code (CSharp):
    1.  
    2.                 Debug.Log("CALLED");
    3.  
    4.                 conn = FindObjectOfType<DBConnector>();
    5.                 conn.registerUser(user);
    6.  
    7.                 Debug.Log("ALSO CALLED");
    8.  
    I have no errors.... but registerUser is not call at all...the first line which is a debug.log is not executed...
    How can I solve it?

    Thanks
     
    Last edited: Feb 28, 2020
  2. unit_dev123

    unit_dev123

    Joined:
    Feb 10, 2020
    Posts:
    989
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    If you want registerUser to run as a coroutine, you need to call StartCoroutine(), not simply call registerUser().

    If a function contains "yield return" and you run it normally, it returns a lazily-evaluated collection. If you want it to run as a regular synchronous function, get rid of "yield return".
     
  4. Thor-Apps

    Thor-Apps

    Joined:
    Dec 13, 2013
    Posts:
    32
    And how do I get the return value?
    Debug.Log("result is " + cd.coroutine); ==> result is UnityEngine.Coroutine
    Debug.Log("result is " + cd.result); ==> result is UnityEngine.Networking.UnityWebRequestAsyncOperation

    How do I get 0 or 1 (as expected)?
     
  5. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    You need to learn about callbacks (a particular use case of delegates). A delegate is basically a reference to a function that you can pass around like a variable. The simplest way to use delegates is to use System.Action<>, which is a delegate type for a "void" function with any parameters you want. In this case, you want one with an int parameter, and you can use that parameter as your "return value".


    Something like:
    Code (csharp):
    1.  
    2. StartCoroutine(RegisterUser(user, OnComplete) );
    3.  
    4. ...
    5.  
    6. IEnumerator RegisterUser(User user, System.Action<int> callbackOnFinish)
    7.     {
    8.         Debug.Log("a register user");
    9.  
    10.         string action = "insertUser";
    11.         WWWForm form = new WWWForm();
    12.  
    13.         form.AddField("action", action);
    14.  
    15.  
    16.         using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
    17.         {
    18.             yield return www.SendWebRequest();
    19.  
    20.             if (www.isNetworkError || www.isHttpError)
    21.             {
    22.                 Debug.Log(www.error);
    23.                 callbackOnFinish(1);
    24.             }
    25.             else
    26.             {
    27.                 Debug.Log(www.downloadHandler.text);
    28.  
    29.                 callbackOnFinish(0);
    30.             }
    31.         }
    32.     }
    33.  
    34. public void OnComplete(int didError) {
    35.     Debug.Log("Did we get an error?" + didError);
    36. }
    Once you grasp this concept, you can use something called a lambda function to keep the code more organized in cases like this - it's a function without a name that gets defined inline as you need it. The syntax is easy to mess up though which is why I started with the other syntax.
    Code (csharp):
    1. StartCoroutine(RegisterUser(user, (didError) => {
    2.     Debug.Log("Did we get an error?" + didError);
    3. });
    In either case it's important to remember that that code won't be executed until callbackOnFinish(x) is called in the coroutine, which may be many frames later. The code is just sort of stashed away until then.

    Delegates and callbacks are super cool and super useful tools. :)

    (And if you're wondering, this is extremely common in networking code like this)
     
    Last edited: Feb 28, 2020
  6. Thor-Apps

    Thor-Apps

    Joined:
    Dec 13, 2013
    Posts:
    32

    Thank you very much for such a good explanation. I have a doubt refering callbacks...

    My coroutine is within a method which is the one I want to return a value to the original called, like this :
    Code (CSharp):
    1.     public int registerUser(User user)
    2.     {
    3.         CoroutineWithData cd = new CoroutineWithData(this, RegisterUser(user));
    4.         return 1;
    5.         Debug.Log("result is " + cd.result);  //  'success' or 'fail'
    6.     }
    Now with the callback I made :
    Code (CSharp):
    1.     public int registerUser(User user)
    2.     {
    3.         int result = -1;
    4.        
    5.         StartCoroutine(RegisterUser(user, (didError) => {
    6.             Debug.Log("Did we get an error?" + didError);
    7.             result = didError;
    8.         });
    9.    
    10.         return result;
    11.     }
    12.    
    Which as you may know it doesn't work.... as you said the method returns a value before the callback is call so always returns "-1".
    So how can I make the callback return the correct value for registerUser method?
     
  7. TomPo

    TomPo

    Joined:
    Nov 30, 2013
    Posts:
    86
    that's because StartCouroutine is like 'fire and forget' so the next line (return result) is being executed before even Ienumerator started so it has a value -1.

    So in general your approach is wrong.
    You want to StartCoroutine and then wait for the result but inside an int calculation which is the main thread
    - this has no logic ;)

    My approach - not tested but should work

    Code (CSharp):
    1. StartCoroutine(RegisterUser(new User()));
    2.  
    3.     IEnumerator RegisterUser(User user) {
    4.         int userID = -1;
    5.         yield return StartCoroutine(GetRegisterID(user, value => userID = value));
    6.         //now you hava a result and you may continue with registration
    7.         Debug.Log(userID);
    8.     }
    9.  
    10.     IEnumerator GetRegisterID(User user, System.Action<int> result) {
    11.         yield return new WaitForSeconds(1); //do something long...
    12.         int regID = 12345;
    13.         result(regID);
    14.     }
     
    PraetorBlue likes this.
  8. SupremePanda

    SupremePanda

    Joined:
    Mar 9, 2019
    Posts:
    3
    Thanks @Thor-Apps callback looks like a best solution.
     
  9. schashm3

    schashm3

    Joined:
    Oct 9, 2018
    Posts:
    10
    my solution is this:

    Code (CSharp):
    1. IEnumerator coroutine(Action<string> action)
    2.     {
    3.         action( "some text");
    4.         yield return action;
    5.     }
    and then call it like this:
    Code (CSharp):
    1. StartCoroutine(coroutine((a) =>
    2.             {
    3.                 string value = a; //return a ("some text")
    4.             }));
     
    zeimhall, pigmie25 and WaqasGameDev like this.