3

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");
    }
}    
Mai Kar
  • 138
  • 1
  • 3
  • 18
  • Could you post how the method `GetToBackOffice()` is being called? Is it from Update() or somewhere else? – S.Fragkos Jan 17 '18 at 15:13
  • @S.Fragkos It is called from Start() – Mai Kar Jan 17 '18 at 15:21
  • Would this help: according to the accepted answer of [this](https://stackoverflow.com/questions/38645336/use-nets-own-httpclient-class-on-unity) question you can target .Net 4.5 and use the 'real' HttpClient that uses Tasks than can be awaited. – Peter Bons Jan 17 '18 at 17:07
  • @PeterBons Actually it is the first thing I tried. Unfortunately, even when targetting .Net 4.5 I couldn't get HttpClient to work. It is not references by the c# project and adding the dll didn't solve the problem. There is [this thread](https://forum.unity.com/threads/httpclient.460748/) on the subject so the Unity development team is aware of the issue but it doesn't seems that anybody managed to make it really work. I thought I'll try another way after spending two days trying to make it work this way. – Mai Kar Jan 17 '18 at 17:23
  • Ok, but you can use async/await in Unity in .Net 4.5 isn't it? If so, I might have a solution. – Peter Bons Jan 17 '18 at 17:25
  • @PeterBons According to [this article](http://www.stevevermeulen.com/index.php/2017/09/using-async-await-in-unity3d-2017/) yes, those features are available now. – Mai Kar Jan 17 '18 at 17:34

3 Answers3

2

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.

Peter Bons
  • 26,826
  • 4
  • 50
  • 74
  • It is Unity and Mono. There is no Task class and no async available. – Everts Jan 17 '18 at 22:08
  • @Everts, according to the comment of the OP it is, he linked to http://www.stevevermeulen.com/index.php/2017/09/using-async-await-in-unity3d-2017/. But updated my answer to make it more clear – Peter Bons Jan 17 '18 at 22:10
  • 1
    @PeterBons Thank you for your answer. Finally I gived up on using .Net 4.5 for several reasons : I couldn't get System.Threading.Task to work properly and anyway, this .Net version made visual studio debugger unusable (crashing at start), making me unable to debug my Unity project which is not possible for me ([seems it was fixed though] (https://issuetracker.unity3d.com/issues/unity-crashes-when-debugging-with-vs2017-when-using-net-4-dot-6)) but I can not really update right now). I think I finally managed to get what I wanted with coroutines (I edited my post with that). – Mai Kar Jan 19 '18 at 13:44
1

Write a coroutine like

  //Class scope variable is neede to hold Response other wise it will 
  //be destroied as soon as function is ended
  ResponseType  response;
  IEnumerator WaitForResponce(ResponseType  response)
  {
        this.response = response;
        while(this.response.Data == null)
        yield return new WaitForSeconds (0.02f);
   //do what you want here

  }

and call the coroutine

 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);
            StartCoroutine(WaitForResponse(response));
        }
        else
        {
            idProcess = null;
        }
    });
alfred
  • 43
  • 8
Nain
  • 1,204
  • 1
  • 12
  • 17
  • Thank you, this looks great, I'm trying that! – Mai Kar Jan 17 '18 at 15:32
  • I have trouble implementing that because this method's class is static while StartCoroutine has to be called from an instance, I'm trying to make a Singleton of the class, I'll let you know. – Mai Kar Jan 17 '18 at 16:24
  • One questions: In your code, when the response callback is raised, the response has already been received so the WaitForResponce coroutine shouldn't be started there, right? – Mai Kar Jan 17 '18 at 17:11
  • No it should start here this coroutine will wait for data to be loaded. Similar concept is used for loading texture from web that taks some time to download after responce is recieved – Nain Jan 18 '18 at 03:34
  • Alrignt, now I don't understand how to return the idProcess value. The thing is that I then use it in the calling class `var idProcess = Utils.GetToBackOffice(String.Format("Events/{0}/infos",id));`. So I don't see how to send the idProcess from the coroutine to the Action class. I completed my post so that it is clearer. – Mai Kar Jan 18 '18 at 09:54
-1

If the GetString method is returning a Task object then you can use the Wait() method to let the Task finished processing before a result is returned.

 var task = httpClient.GetString({
    impl here..
 });
 
 task.Wait();
 return idProcess;