Getting return value of a Coroutine

Hi,
I’ll like to receive the value of a couroutine.
Here is what I did…

public class CoroutineWithData
{
    public Coroutine coroutine { get; private set; }
    public object result;
    private IEnumerator target;
    public CoroutineWithData(MonoBehaviour owner, IEnumerator target)
    {
        this.target = target;
        this.coroutine = owner.StartCoroutine(Run());
    }

    private IEnumerator Run()
    {
        while (target.MoveNext())
        {
            result = target.Current;
            yield return result;
        }
    }
}
public class DBConnector : MonoBehaviour
{

    string BASE_URL = "TO_MY_SITE";

    public DBConnector()
    {
    }

    public IEnumerator registerUser(User user)
    {
        Debug.Log("NEVER GOES HERE");

        CoroutineWithData cd = new CoroutineWithData(this, RegisterUser(user));
        yield return cd.result;
        Debug.Log("result is " + cd.result);  //  'success' or 'fail'

        //yield return cd.result;

       // StartCoroutine(RegisterUser(user));
    }

    IEnumerator RegisterUser(User user)
    {
        Debug.Log("a register user");

        string action = "insertUser";
        WWWForm form = new WWWForm();

        form.AddField("action", action);


        using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
        {
            yield return www.SendWebRequest();

            if (www.isNetworkError || www.isHttpError)
            {
                Debug.Log(www.error);
                yield return 1;
            }
            else
            {
                Debug.Log(www.downloadHandler.text);

                yield return 0;
            }
        }
    }
}

And the I call the DBConnector from a Button action method…

                Debug.Log("CALLED");

                conn = FindObjectOfType<DBConnector>();
                conn.registerUser(user);

                Debug.Log("ALSO CALLED");

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

maybe How do I return a value from a coroutine? - Questions & Answers - Unity Discussions

I imagine it has similiar concept to async await

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”.

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)?

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:

StartCoroutine(RegisterUser(user, OnComplete) );

...

IEnumerator RegisterUser(User user, System.Action<int> callbackOnFinish)
    {
        Debug.Log("a register user");

        string action = "insertUser";
        WWWForm form = new WWWForm();

        form.AddField("action", action);


        using (UnityWebRequest www = UnityWebRequest.Post(BASE_URL + "userAPI.php", form))
        {
            yield return www.SendWebRequest();

            if (www.isNetworkError || www.isHttpError)
            {
                Debug.Log(www.error);
                callbackOnFinish(1);
            }
            else
            {
                Debug.Log(www.downloadHandler.text);

                callbackOnFinish(0);
            }
        }
    }

public void OnComplete(int didError) {
    Debug.Log("Did we get an error?" + didError);
}

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.

StartCoroutine(RegisterUser(user, (didError) => {
    Debug.Log("Did we get an error?" + didError);
});

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. :slight_smile:

(And if you’re wondering, this is extremely common in networking code like this)

12 Likes

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 :

    public int registerUser(User user)
    {
        CoroutineWithData cd = new CoroutineWithData(this, RegisterUser(user));
        return 1;
        Debug.Log("result is " + cd.result);  //  'success' or 'fail'
    }

Now with the callback I made :

    public int registerUser(User user)
    {
        int result = -1;
       
        StartCoroutine(RegisterUser(user, (didError) => {
            Debug.Log("Did we get an error?" + didError);
            result = didError;
        });
   
        return result;
    }

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?

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 :wink:

My approach - not tested but should work

StartCoroutine(RegisterUser(new User()));

    IEnumerator RegisterUser(User user) {
        int userID = -1;
        yield return StartCoroutine(GetRegisterID(user, value => userID = value));
        //now you hava a result and you may continue with registration
        Debug.Log(userID);
    }

    IEnumerator GetRegisterID(User user, System.Action<int> result) {
        yield return new WaitForSeconds(1); //do something long...
        int regID = 12345;
        result(regID);
    }
1 Like

Thanks @Thor-Apps callback looks like a best solution.

my solution is this:

IEnumerator coroutine(Action<string> action)
    {
        action( "some text");
        yield return action;
    }

and then call it like this:

StartCoroutine(coroutine((a) =>
            {
                string value = a; //return a ("some text")
            }));
4 Likes