-2

I have this method that downloads data from the internet:

    public ObservableCollection<Magnetka> ParseJSON() {
            ObservableCollection<Models.Magnetka> Markers = new ObservableCollection<Models.Magnetka>();
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("(URL)");
            request.Headers.Add("Authentication-Token", "(KEY)");
            request.Method = "GET";
            request.ContentType = "application/json";
            CookieContainer cookieContainer = new CookieContainer();
            request.CookieContainer = cookieContainer;
            var response = request.GetResponse();
            var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
            ......... JSON parsing)

            return Markers;

Now I found out this isn't too fast and it freezes the app for two seconds or so, so I need to make it async. However, I call the method like this in another class:

        GetMarkers getMarkers = new GetMarkers();
        Markers = getMarkers.ParseJSON();

How can I make my method async? I don't understand the concept well and if I make ParseJSON() async Task, I don't know where to put the "await" keyword. Can anyone help me?

Etoile
  • 161
  • 10
  • Is there a request.GetResponseAsync()? The methods that are calling "other systems" are prime candidates for awaiting – Hans Kesting Jan 02 '21 at 14:04
  • Does this answer your question? [How and when to use ‘async’ and ‘await’](https://stackoverflow.com/questions/14455293/how-and-when-to-use-async-and-await) – Cfun Jan 02 '21 at 14:04

2 Answers2

1

first, mark the method async and use the async version of GetResponse

public async Task<ObservableCollection<Magnetka>> ParseJSON() 
{
    ...
    var response = await request.GetResponseAsync();
    ...

    return Markers;
}

then when you call it use the await keyword

Markers = await getMarkers.ParseJSON();
Jason
  • 86,222
  • 15
  • 131
  • 146
  • Thank you so much for a simple and clear response! :) I will mark your answer as accepted when I can, thank you! – Etoile Jan 02 '21 at 14:07
1

You would change your method to something like this instead:

public async Task<ObservableCollection<Magnetka>> ParseJSON()
{
    var markers = new ObservableCollection<Magnetka>();
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create("(URL)");
    request.Headers.Add("Authentication-Token", "(KEY)");
    request.Method = "GET";
    request.ContentType = "application/json";
    CookieContainer cookieContainer = new CookieContainer();
    request.CookieContainer = cookieContainer;
    using var response = await request.GetResponseAsync().ConfigureAwait(false);
    using var responseStream = response.GetResponseStream();
    using var streamReader = new StreamReader(responseStream);
    var json = await streamReader.ReadToEndAsync().ConfigureAwait(false);

    // json parsing

    return markers;
}

Firstly, you would mark your method async. This signals the compiler to set up a state machine for this method for continuation. Nothing async happening yet though.

Another thing you need to do is to wrap your return value in a Task, which is similar to a promise in some other languages. Tasks are await-able, which is where the asynchronous stuff is happening.

Switching from using the synchronous methods in WebRequest to use the Async variants and awaiting their results makes your method async.

So from:

request.GetResponse()

To:

await request.GetResponseAsync()

Which returns a awaitable Task<WebResponse>.

The same goes for the stream reader code, you can use its async API too.

A tricky thing by using asynchronous code is that, it kind of forces you to do this all the way up. However, there are exceptions for lifecycle methods and events, where you don't necessarily have to change the signature to return a Task.

Another thing you might notice here. I've added ConfigureAwait(false) at the end of the async method calls. I've done this because out of the box, after returning from an awaited method, the code will try to return to the originating context. This can lead to a couple of issues such as dead-locks if the originating context is the UI Thread. Or, it can lead to bad performance trying to switch between context a lot. Adding ConfigureAwait(false) will just return on the same context the await was run on, which is fine as long as we don't need or expect to return on that context.

As for where you call ParseJson() is probably equally important. Consider deferring it to some lifecycle method or event where you are not doing much else.

This could be OnResume() on Android or ViewWillAppear on iOS. If you are using MVVM, use a Command to encapsulate it.

Cheesebaron
  • 24,131
  • 15
  • 66
  • 118