Morse
Morse

Reputation: 9144

UWP await on HttpClient not working

I am trying get a JSON response from a web API.

I am able to retrive response in Console application with similar code, however in UWP await httpClient.GetAsync(uri); does not work as expected.

public static async Task<double> GetJson()
{
    using (var httpClient = new HttpClient())
    {
        Uri uri= new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
        HttpResponseMessage response = await httpClient.GetAsync(uri);
        //Below code is not relevent since code is failing on await
        var result = response.Content.ReadAsStringAsync();
        var jsonResponse = Json.ToObjectAsync<ExchangeRate>(result);//
        jsonResponse.Wait();//
        ExchangeRate exchangeRateObj = jsonResponse.Result;//
        return 1.2;//
    }

}

Code behind:

private void Button_Click(object sender,RoutedEventArgs e){

var ROC__ =  MyClass.GetJson();
ROC__.Wait();
currency_.ROC = ROC__.Result;

}

What is does not work here means?

It is supposed to connect to URL and fetch the response and response should be assigned some value. Instead on debug with either step into or Continue the control exits the current line skips subsequent lines too. (I have put debug on next lines too),The app just freezes.

I refereed similar codes for JSON parsing with HTTPClient on Stackoverflow and other blogs , its suggested to use System.Net.Http or Windows.Web.Http

Related Question : how-to-get-a-json-string-from-url

I think tasks are run and its going on forever wait mode , which seems strange as debug mode doesnt show code being run , it just show ready . There is no exception as well.

Am I doing something wrong or missing some Nuget reference?

Please suggest.

PS : Its the same case with httpClient.GetStringAsync method too. On Console app this line works but not on UWP

 var json = new WebClient().DownloadString("https://api.cryptonator.com/api/ticker/btc-usd");

Not duplicate of httpclient-getasync-never-returns-on-xamarin-android

Upvotes: 1

Views: 1688

Answers (3)

Aly Elhaddad
Aly Elhaddad

Reputation: 1953

There're several errors with the code specified that needs to be cured. First, mark your event handler async:

private async void Button_Click(object sender, RoutedEventArgs e)

Second, await GetJson and since this is an asynchronous method, it is a better convention to add the suffix "Async" to it's name; therefore, await GetJsonAsync:

currency_.ROC = await MyClass.GetJsonAsync();

Now, back to GetJsonAsync itself, ReadAsStringAsync and ToObjectAsync should be awaited too:

private static async Task<double> GetJsonAsync()
{
    using (var httpClient = new HttpClient())
    {
        Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
        HttpResponseMessage response = await httpClient.GetAsync(uri);
        string result = await response.Content.ReadAsStringAsync();
        //Below code is not relevent since code is failing on await
        ExchangeRate exchangeRateObj = await Json.ToObjectAsync<ExchangeRate>(result);
        return 1.2;//
    }
}

The reason it wasn't working before is that there was a deadlock caused by the context switch between the await call and the synchronous block of .Wait(). Find more about that here.

Upvotes: 2

TheTanic
TheTanic

Reputation: 1638

So I tried this by myself. Unfortunately your informations where not complete so a little header:
For the Json-Handling I used Newtonsoft, because I didnt found the Json.ToObjectAsync in my UWP environment.
To create the ExchangeRate- class I used Json2CSharp.

Here are the ExchangeRate classes:

public class ExchangeRate
    {
        public string Error { get; set; }
        public bool Success { get; set; }
        public Ticker Ticker { get; set; }
        public int Timestamp { get; set; }
    }

public class Ticker
    {
        public string @Base { get; set; }
        public string Change { get; set; }
        public string Price { get; set; }
        public string Target { get; set; }
        public string Volume { get; set; }
    }

I changed the Button_Click-Method to an async void. Normally its not recommend to have an async void instead of a async Task. But because it is a Handler from a UI-Element its not a problem, because the source will not wait anyway and you shouldn't call this methode directly from your code-behind.
The Button_Click-Method:

private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var ROC__ = await MyClass.GetJson();
            //Do whatever you want with the result.
            //Its a double, maybe you want to return an ExchangeRate objcet insted
        }

Inside of your GetJson-Method, you need to add an await for your async operations, or add the .Wait() directly after the method and not in a new line. You need to do this, because the Task automatically starts to run, when you call the async operation and the .Wait() comes to late. So your GetJson-Method looks like this:

public static async Task<Double> GetJson()
        {

            using (var httpClient = new HttpClient())
            {
                Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
                HttpResponseMessage response = await httpClient.GetAsync(uri);
                if (response.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    var result = await response.Content.ReadAsStringAsync();
                    ExchangeRate rate = JsonConvert.DeserializeObject<ExchangeRate>(result); //Newtonsoft
                    return 1.2;
                }
                else
                {
                    return -1; //Example value
                }
            }
        }

In addition I added a check, if the Request was successful, to be sure, that we have a response. How I said before: I think you should return a ExchangeRate-object instead of a double, but this depends on your context.

Upvotes: 1

DotNetRussell
DotNetRussell

Reputation: 9867

Your code works fine. I regenerated it here below. Just get rid of that wonky wait call you're doing.

Do this. Create a new uwp app, paste in the below code and put a breakpoint on the return and see that it gets executed.

It will work regardless if you make your button handler async or not. If you don't make it asnyc though then the request won't be executed asynchronously

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        SomeClass.GetJson();
    }

}

public class SomeClass
{
    public static async Task<double> GetJson()
    {
        using (var httpClient = new HttpClient())
        {
            Uri uri = new Uri("https://api.cryptonator.com/api/ticker/btc-usd");
            HttpResponseMessage response = await httpClient.GetAsync(uri);
            return 1.2;
        }
    }
}

Might I take this moment for a shameless plug of my UWP lib. It does this work for you.

https://github.com/DotNetRussell/UWPLibrary

In the BasecodeRequestManager.cs file there's a BeginRequest function that does this work async for you. The lib has many many other features as well.

Upvotes: 1

Related Questions