Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for a callback before returning

I am trying to port a code from .Net to Unity C# and I am stuck on a syntax including a callback.

Basically, I had to replace the .Net 'HttpClient' library by this one, also called 'HttpClient'. But the 'Get' syntax is not the same and uses a Callback. I am quite new to C# and Http queries and don't know how to deal with this syntax to get the expected return.

The original function written in .Net:

internal static JObject GetToBackOffice(string action)
{
    var url = backOfficeUrl;
    url = url + action;

    HttpClient httpClient = new HttpClient();

    var response =
        httpClient.GetAsync(url
        ).Result;
    if (response.StatusCode == System.Net.HttpStatusCode.OK)
    {
        var idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Content.ReadAsStringAsync().Result);

        return idProcess;
    }
    else
        return null;

The C# code I am writing for Unity:

internal class Utils
{ 
    internal static JObject GetToBackOffice(string action)
    {
        var url = backOfficeUrl;
        url = url + action;

        HttpClient httpClient = new HttpClient();

        JObject idProcess = new JObject();

        httpClient.GetString(new Uri(url),
            (response) =>
            {
                // Raised when the download completes
                if (response.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    idProcess = Newtonsoft.Json.Linq.JObject.Parse(response.Data);
                }
                else
                {
                    idProcess = null;
                }
            });
        // Here I would like to wait for the response, so that idProcess is filled with the received data before returning
        return idProcess;
    } 

public class Action
{
     public bool SendData(string id, string secretKey, FakeData data)
    {
        var idProcess = Utils.GetToBackOffice(String.Format("Events/{0}/infos",id));
        //...I do then something with idProcess
        //Currently, when I use idProcess here, it is still empty since the GetString response hasn't been received yet when this line is executed

        return true;
    }
}

public class EventHubSimulator : MonoBehaviour
{
    void Start()
    {
        //Fill the parameters (I skip the details)
        string oId = ...;
        string secretKey = ...;
        var vh = ...;

        Action action = new Action();
        action.SendData(oId, secretKey, vh);
    }
}

My issue is that after the GetToBackOffice function, my code directly uses 'idProcess' for something else but this object is empty because the response was not received yet. I would like to wait for the response before my function returns.

I hope I was clear enough. I know that similar question have already been posted but couldn't find a solution to my specific issue.


Edit:

Finally I used a coroutine as Nain suggested but couldn't get what I expected the way he said. This way seems to work (event though it might not be a good way to do it).

public class EventHubSimulator : MonoBehaviour
    {
        void Start()
        {
            //Fill the parameters (I skip the details)
            string oId = ...;
            string secretKey = ...;
            var vh = ...;

            Utils utils = new Utils();
            StartCoroutine(utils.SendData(oId, secretKey, vh));
        }
    }

public class Utils: MonoBehaviour
{
    private const string backOfficeUrl = "http://myurl/api/";

    public CI.HttpClient.HttpResponseMessage<string> response;

    public IEnumerator SendData(string id, string secretKey, FakeData data)
    {
        response = null;
        yield return GetToBackOffice(String.Format("Events/{0}/infos", id)); //Make a Http Get request
        //The next lines are executed once the response has been received
        //Do something with response
        Foo(response);
    }

    IEnumerator GetToBackOffice(string action)
    {
        var url = backOfficeUrl;
        url = url + action;

        //Make a Http Get request
        HttpClient httpClient = new HttpClient();
        httpClient.GetString(new Uri(url), (r) =>
        {
            // Raised when the download completes
            if (r.StatusCode == System.Net.HttpStatusCode.OK)
            {
                //Once the response has been received, write it in the global variable
                response = r;
                Debug.Log("Response received : " + response);
            }
            else
            {
                Debug.Log("ERROR =============================================");
                Debug.Log(r.ReasonPhrase);
                throw new Exception(r.ReasonPhrase);
            }
        });

        //Wait for the response to be received
        yield return WaitForResponse();
        Debug.Log("GetToBackOffice coroutine end ");
    }

    IEnumerator WaitForResponse()
    {
        Debug.Log("WaitForResponse Coroutine started");
        //Wait for response to become be assigned
        while (response == null) 
        {
            yield return new WaitForSeconds(0.02f);
        }
        Debug.Log("WaitForResponse Coroutine ended");
    }
}    
like image 601
Mai Kar Avatar asked Sep 10 '25 13:09

Mai Kar


1 Answers

One solution is polling for completion as Nain submitted as an answer. If you don't want polling you can use a TaskCompletionSource. This Q&A dives a bit deeper into the why and how.

Your code can then be written like this:

async Task CallerMethod()
{
    JObject result = await GetToBackOffice(...);
    // Do something with result
}

internal static Task<JObject> GetToBackOffice(string action)
{
    var tsc = new TaskCompletionSource<JObject>();
    var url = backOfficeUrl;
    url = url + action;

    HttpClient httpClient = new HttpClient();

    JObject idProcess = new JObject();

    httpClient.GetString(new Uri(url),
        (response) =>
        {
            // Raised when the download completes
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                tsc.SetResult(Newtonsoft.Json.Linq.JObject.Parse(response.Data));
            }
            else
            {
                tsc.SetResult(null);
            }
        });

    return tsc.Task;
}

See also the section Async Method Calls a Coroutine And Wait for Completion of this msdn blogpost.

NOTE Tasks, and async/await support is only available as beta functionality in Unity. See also this post.

like image 52
Peter Bons Avatar answered Sep 13 '25 04:09

Peter Bons