bertibott
bertibott

Reputation: 33

Trying to call async method - await vs GetAwaiter().GetResult();

I am trying to write a class with a method that will get some data from a REST API (the googleapi for youtube playlists) and dump that data into an ObservableCollection. The problem I am encountering is that when I call this:

this.getPlaylistItemsAsync().GetAwaiter().GetResult();

the code executes.

When I call this:

await this.getPlaylistItemsAsync();

it simply skips over the code.

I am pretty new to the whole asynchrounous programming thingy... I thikn I understand the basic concepts.. I just can't figure out how to do it :(

The full code is here:

#define DEBUG
//#define TestEnvi

using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Newtonsoft.Json;



namespace IntuifaceYTPlaylist {

public class IFYT_Playlist {
    
    public string playlistID {get;set;}= "PLYu7z3I8tdEmpjIoZHybXD4xmXXYrTqlk";  // The ID with which we identify our playlist
    private string key = "supersecretAPIkey";    // The API-Key we use to access the google-API
    public ObservableCollection<string> videosList {get;set;}= new ObservableCollection<string>();

    public IFYT_Playlist () {
        Console.WriteLine("Class Loaded!");
#if TestEnvi
        this.getPlaylistItemsAsync().GetAwaiter().GetResult();
        Console.WriteLine("This worked!");
        await this.getPlaylistItemsAsync();
        Console.WriteLine("This didn't!"); //In fact this line isn't even executed :(
#endif
    }

#region getData

    public async void update (){
        this.getPlaylistItemsAsync().GetAwaiter().GetResult();
    }

    // Access the REST-API to retrieve the full dataset
    // TODO: Manage connection to the API - DONE
    // TODO: Parse JSON
    public async Task<ObservableCollection<string>> getPlaylistItemsAsync(){

        var output = new ObservableCollection<string>();

        string URL = "https://www.googleapis.com/youtube/v3/playlistItems";
        HttpClient client = new HttpClient();

        string query = URL + "?key=" + this.key + "&part=contentDetails&playlistId=" + this.playlistID + "&maxResults=50";

        var response = await client.GetStringAsync(query);  //Dump the JSON string into an object!

# if DEBUG
        // Dump the response into a file - just so we can check it if something goes wrong...
        using (StreamWriter outputFile = new StreamWriter(Path.Combine("", "Dump.json")))
        {
                outputFile.WriteLine(response);
        }
#endif

        var responseData = JsonConvert.DeserializeObject<dynamic>(response);

        // Iterate over the items in the list to get the VideoIDs of the individual videos
        foreach (var item in responseData.items){
            output.Add(JsonConvert.SerializeObject(item.contentDetails.videoId));
        }
        Console.WriteLine(output);

#if DEBUG   //Let's see if that worked....
        Console.WriteLine();
        Console.WriteLine("Printing VideoIDs:");
        Console.WriteLine();
        foreach (var item in output){
            Console.WriteLine(item);
        }
        Console.WriteLine();
#endif


    this.videosList = output;
    return output;    
    }
#endregion
}

}

The program I use to call this looks like this:

using System;
using IntuifaceYTPlaylist;

namespace TestEnvi
{
    class Program
    {
        static void Main(string[] args)
        {            
            Create();
        }

        static async void Create(){
            IFYT_Playlist playlist = new IFYT_Playlist();
            playlist.getPlaylistItemsAsync().GetAwaiter().GetResult();
            Console.WriteLine("This worked!");
            await playlist.getPlaylistItemsAsync();
            Console.WriteLine("Did this?");
        }
    }
}

Upvotes: 0

Views: 4831

Answers (2)

E. Shcherbo
E. Shcherbo

Reputation: 1148

It's hard to tell from your code example, where exactly you're trying to update the list, because the code which you are pointing out to can't even be compiled. You can't use the await keyword in constructors, because constructors can't be async.

The main reason why you don't see console output is because your Main method doesn't wait for your async void Create to finish, because Create is async operation and as we know it's non blocking. Therefore your main thread just triggers the Create operation and continue doing other stuff below. But there is nothing below and that's it, the program is done.

To fix it, you should either turn your Main method to async method and call await Create(), or just call Create().Wait. You can also call Console.ReadLine if this is the console application.

By the way, it works with this.getPlaylistItemsAsync().GetAwaiter().GetResult();, because it's not asynchronous code any more, your Create method is blocked and it waits for the getPlaylistItemsAsync to finish. Similarly your Main method also waits for the Create to return, so that the program keep working.

You also have async void update method which you probably use somewhere (I don't know), so be careful with it since you don't have a way to know whether the method has finish its operation or not. Below is simplified explanation of asynchronous code in C# and why it might be bad to return void from the async method.

A bit about asynchronousy

The way how we can describe an asynchronous operations is that the operation is not blocking which means that you can tell that you want to begin the operation and continue doing other stuff not waiting the operation to finish its work. But this is not very useful to just start the operation (sometimes it might be useful), you usually want to do something when the operation is completed, failed etc. So in .NET the async operation returns Task or Task<T> which is the abstraction over the operation which might be completed or might be still in progress. Then you can attach your own code to be executed when a task is completed.

What happens under the hood when you await an operation

Keep in mind that this description is too simplified than what is actually happening.

So when you do something like this:

 public async Task IFYT_Playlist () {
    await this.getPlaylistItemsAsync();
    ... Do some other stuff when the `getPlaylistItemsAsync` has been completed
 }

The IFYT_Playlist method attaches the code which is below await this.getPlaylistItemsAsync(); to the task which is returned by the method and return immediately. Again this is not what's actually happening, but can get you a basic idea:

public async Task IFYT_Playlist () {
    Task resTask = this.getPlaylistItemsAsync();
    return resTask.ContinueWith(t => ...Do some other stuff when the `getPlaylistItemsAsync` has been completed);
 }

So that as it turned out async operation should return Task so that the caller will be able to attach a continuation to it (with ContinueWith) method. Do you see the problem now? If your return type is void, the caller won't be able to attach the code which should be executed when the operation has been completed. So, if getPlaylistItemsAsync's returns type is void, you can't await this.getPlaylistItemsAsync(), because it's impossible to call ContinueWith on nothing (void). Therefore you can't even have await with method that returns void.

 public async Task IFYT_Playlist () {
    await this.getPlaylistItemsAsync(); // doesn't compile
 }

Therefore your update method should return Task or Task<T> for any suitable T in your case rather than nothing (void).

Upvotes: 2

Miguel Gamboa
Miguel Gamboa

Reputation: 9373

Invoked method immediately returns to the caller (i.e. to the Main) on first await and from there, both (the caller and the invoked method) run concurrently. Yet, since the Main finishes first, the whole program ends.

Thus, if you want to wait for asynchronous call completion you may change Create() to return Task rather than void and then you can wait such as: Create().Wait();

Upvotes: 2

Related Questions