1

in my Windows Phone 8 application, I have a LoadData() method in my file MainViewModel.cs.

This method load data from a WCF service with entity framework...

Then, in my pages, I call LoadData()

The LoadData() method :

public void LoadData()
{
    client.GetMoviesCompleted += new EventHandler<ServiceReference1.GetMoviesCompletedEventArgs>(client_GetMoviesCompleted);
    client.GetMoviesAsync();

    client.GetTheatersCompleted += new EventHandler<ServiceReference1.GetTheatersCompletedEventArgs>(client_GetTheatersCompleted);
    client.GetTheatersAsync();

    this.IsDataLoaded = true;
}

With the methods :

private void client_GetMoviesCompleted(object sender, ServiceReference1.GetMoviesCompletedEventArgs e)
{
    Movies = e.Result;
}

private void client_GetTheatersCompleted(object sender, ServiceReference1.GetTheatersCompletedEventArgs e)
{
    Theaters = e.Result;
}

Then in my pages :

 App.ViewModel.LoadData();

The problem is that it doesn't wait until the data is loaded.

Can you help me to use Async/Await the LoadData() method to wait until the data is loaded ?

Thanks

Alaa Masoud
  • 7,085
  • 3
  • 39
  • 57
user2157493
  • 31
  • 1
  • 7

4 Answers4

4

So we'll start with these two methods that convert your existing methods from an event-based model into a task based model. You'll need to modify them slightly to line up with your types as I don't quite have enough information to replicate them completely, but the remaining change should be small:

public static Task<Movie[]> WhenGetMovies(MyClient client)
{
    var tcs = new TaskCompletionSource<Movie[]>();
    Action<object, Movie[]> handler = null;
    handler = (obj, args) =>
    {
        tcs.SetResult(args.Result);
        client.GetMoviesCompleted -= handler;
    };
    client.GetMoviesCompleted += handler;
    client.GetMoviesAsync();
    return tcs.Task;
}
public static Task<Theater[]> WhenGetMovies(MyClient client)
{
    var tcs = new TaskCompletionSource<Theater[]>();
    Action<object, Theater[]> handler = null;
    handler = (obj, args) =>
    {
        tcs.SetResult(args.Result);
        client.GetTheatersCompleted -= handler;
    };
    client.GetTheatersCompleted += handler;
    client.GetTheatersAsync();
    return tcs.Task;
}

Now that we can get tasks that represent the completion of these async operations loading the data is easy:

public async Task LoadData()
{
    var moviesTask = WhenGetMovies(client);
    var theatersTask = WhenGetTheaters(client);
    var movies = await moviesTask;
    var theaters = await theatersTask;
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • What about using await Task.WhenAll(WhenGetMovies(client),WhenGetTheaters(client)); since it don't seems the two method need to be executed in that order – Fabio Marcolini Jun 27 '13 at 14:06
  • @FabioMarcolini The results of the two methods are different types, so you wouldn't be able to meaningfully get the results of the tasks doing that without casting them from `object`. Doing that is more work than what I've done here, and less safe. – Servy Jun 27 '13 at 14:07
  • oh yeah, sorry didn't realized they weren't returning a Task – Fabio Marcolini Jun 27 '13 at 14:08
  • Ok, I tried with only the theaters. Here is my code http://pastebin.com/Sg0vwnUJ , I got errors on "Result" and "handler" : https://dl.dropboxusercontent.com/u/9197067/errors.png – user2157493 Jun 27 '13 at 14:26
  • @user2157493 In your case you don't have an `Action` you have an `EventHandler`. Change the type of `handler` to match what your events actually define; it should be the same type used when assigning an event handler in your old code. – Servy Jun 27 '13 at 14:33
  • Ok, sorry , I am a beginner and I don't manage to make it works... But thank you very much for your help... I am not even sure that my problem comes from that, because the data load correctly in my app in normal cases. I only have an error when a try to navigate directly to a specific movie details page from a secondary tile i created, like I explained here : http://stackoverflow.com/questions/17333586/windows-phone-link-from-tile-error – user2157493 Jun 27 '13 at 14:47
  • I managed to remove the errors but then, how do I call the method ? I tried "await App.ViewModel.LoadData();" but I got the error "The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'" – user2157493 Jun 27 '13 at 15:41
  • @user2157493 Yep. I would advise you to consider the message very seriously. You almost certainly want to do as it advises and mark that method as `async` as well, and adjust it's signature accordingly. This is what is meant by "async all the way up". – Servy Jun 27 '13 at 15:43
0

The problem is, when you execute your LoadData() method, the runtime don't wait to continue the execution of your method. You can simply do a think like this :

private bool _moviesLoaded;
private bool _theatersLoaded;

private void client_GetMoviesCompleted(object sender, ServiceReference1.GetMoviesCompletedEventArgs e)
{
    Movies = e.Result;
    _moviesLoaded = true;
    TrySetDataIsLoaded();
}

private void client_GetTheatersCompleted(object sender, ServiceReference1.GetTheatersCompletedEventArgs e)
{
    Theaters = e.Result;
    _theatersLoaded = true;
    TrySetDataIsLoaded();
}

private void TrySetDataIsLoaded()
{
     if(_moviesLoaded && _theatersLoaded) this.IsDataLoaded = true;
}

If you want to use async and await, you can try to work with TaskCompletionSource

Joffrey Kern
  • 6,449
  • 3
  • 25
  • 25
  • Thanks. I don't necessarily need to use asyn/await. But then, how do I waint until IsDataLoaded = true in my pages ? – user2157493 Jun 27 '13 at 13:56
  • If _moviesLoaded & _theatersLoaded are true, then your data is loaded. – Joffrey Kern Jun 27 '13 at 13:58
  • Yes, but in my page, I call "App.ViewModel.LoadData();" and then I directly need to use my "Movies" but it is null because the load is not finished. In my page, how do I wait until IsDataLoaded = true ? – user2157493 Jun 27 '13 at 14:07
0

The bottom line is that you'll need to design and implement a "loading" state for your application. This is true whether you use event-based asynchronous programming (like your current code) or async/await. You should not synchronously block the UI until the loading is complete.

Personally, I like to (synchronously) initialize everything into the "loading" state, and when the asynchronous loading completes, have it update data-bound items on the View Model. The View then transitions to a "ready" state via data binding.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

make above two variables (private bool _moviesLoaded;private bool _theatersLoaded;) as properties and set them in completed eventhandlers . and till the set is called use loader and when set is called disable this loader and now you can use this data for your work..

loop
  • 9,002
  • 10
  • 40
  • 76