31

I'm building a Metro App.

In the MainPage.xaml.cs, I instantiate Album as follows:

Album album = new Album(2012);  //With the album ID as its parameter.
ListView1.ItemsSource = album.Songs;

In the Album.cs, the constructor is as follows:

public Album(int ID)
{
    this.ID = ID;
    Initialize();  //Serves as a wrapper because I have to call httpClient.GetStreamAsync() and "async" doesn't work for the constructor.
}

Finally, the Initialize method:

private async void Initialize()
{
    //...some code...
    HttpClient cli = new HttpClient();
    Stream SourceStream = await HttpClient.GetStreamAsync("http://contoso.com");
    //...some code...
    this.Songs = Parse(SourceStream);
}

The problem is when it runs to GetStreamAsync, it then goes to ListView1.ItemsSource = album.Songs directly with the album.Songs null.

Is there any quick solution to this problem?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Isilmë O.
  • 1,668
  • 3
  • 23
  • 35
  • `await` doesn't work like that. it _starts_ an asynchronous operation. you need to `await` for the result in your code. – Vlad Sep 02 '12 at 12:34
  • 2
    @Vlad: No, `await` itself doesn't start the asynchronous operation. `await` just hands control back and schedules a continuation (if it needs to, of course). – Jon Skeet Sep 02 '12 at 12:37
  • @Jon: you're right that it hands the control back and that I ought to have mentioned it, but isn't "schedules a continuation" effectively the same as "starts an asynchronous operation"? – Vlad Sep 02 '12 at 12:40
  • 2
    @Vlad: No, it schedules it. The operation doesn't *start* until whichever operation you're awaiting has completed. – Jon Skeet Sep 02 '12 at 12:47
  • @Jon: I was mentioning the inner operation (`GetStreamAsync` in out case). Isn't thinking that `GetStreamAsync` _starts_ asynchronously at `await` (with the possibility of the scheduler to immediately suspend the operation) and finishes at undefined time a good mental model? – Vlad Sep 02 '12 at 13:06
  • 1
    @Vlad: No, because `GetStreamAsync` is what starts the operation. That could happen a long way before the `await` expression - in the asynchronous task-based pattern, tasks are "hot", unlike in F# for example. The `await` expression *only* schedules the continuation where necessary - it doesn't start the operation. – Jon Skeet Sep 02 '12 at 19:37

2 Answers2

42

Yes. The whole point of async and await are that you don't block. Instead, if you're "awaiting" an operation which hasn't completed yet, a continuation is scheduled to execute the rest of the async method, and control is returned to the caller.

Now because your method has a type of void, you have no way of knowing when that's even finished - if you returned Task (which wouldn't require any change in the body of the method) you'd at least be able to work out when it had finished.

It's not really clear what your code looks like, but fundamentally you should only be trying to set the ItemsSource after initialization has finished. You should probably have your MainPage code in an async method too, which would look something like:

Album album = new Album(2012);
ListView1.ItemsSource = await album.GetSongsAsync();

Your GetSongs() call would then be:

private async Task<List<Song>> GetSongsAsync()
{
    //...some code...
    HttpClient cli = new HttpClient();
    Stream SourceStream = await HttpClient.GetStreamAsync("http://contoso.com");
    //...some code...
    return Parse(SourceStream);
}

This means Songs would no longer be a property of Album itself, although you could add it in for caching purposes if you wanted to.

wake-0
  • 3,918
  • 5
  • 28
  • 45
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks for your detailed answer! The problem was solved. But to take it further, is there any alternative to the httpClient.GetStringAsync() method? I mean something like webClient.DownloadString() in the old days. I have to say it's really handy in some ocassions when something like DownloadStringAsync() is not necessary. – Isilmë O. Sep 02 '12 at 13:24
  • 1
    @IsilmëOrphousV: It's still in "normal" .NET 4.5 - but not in WinRT, as far as I'm aware... precisely because you should be doing things in an asynchronous way. – Jon Skeet Sep 02 '12 at 19:36
  • The really should have called it something else, beginAsync maybe. – Paul McCarthy Oct 23 '19 at 10:51
  • Good article on the subject: https://www.pluralsight.com/guides/understand-control-flow-async-await – Marc Roussel Oct 04 '21 at 17:40
5

Make Songs property return Task<List<Song>> and await at ListView1.ItemsSource = await album.Songs;

L.B
  • 114,136
  • 19
  • 178
  • 224
  • But is there any binding-friendly way to accomplish this? – Isilmë O. Sep 02 '12 at 13:48
  • @IsilmëOrphousV What do you mean by `binding-friendly way`? – L.B Sep 02 '12 at 14:27
  • @L-B It works fine with List for I only want to use it for ItemsSource. But if I wanted to bind a Song, which has a property indicating how many times it has been played, a number to be retrieved remotely by asynchronous call, it would be inconvenient to bind the property (Task) to the view. Anyway, I think wrapping it all into a separate method might be a workaround, with more code but more clarity on the other side. – Isilmë O. Sep 02 '12 at 15:23
  • This would mean I'd have to make my calling function `async` too. Isn't it possible to only make the function containing the `HttpClient` call `async`? – Wouter Vanherck Oct 16 '18 at 07:11