2

I have created a Windows 8 Metro App based on the Split Page sample app. However, in the sample app the data is loaded synchronously in the constructor. I'm accessing a text file and therefore need to load the data asynchronously. The constructor looks like this:

    public MyDataSource()
    {
        DataLoaded = false;

        LoadData();
    }

LoadData() is an asynchronous method that populates the data model. This works fine, and displays the data as is loads it (which is the behavior that I want). The problem occurs when I try testing the suspend and terminate. The problem being that the recovery has the potential to attempt to access the data model before it is populated:

    public static MyDataGroup GetGroup(string uniqueId)
    {
        // If the data hasn't been loaded yet then what?
        if (_myDataSource == null)
        {
        // Where app has been suspended and terminated there is no data available yet
        }

        // Simple linear search is acceptable for small data sets
        var matches = _myDataSource.AllGroups.Where((group) => group.UniqueId.Equals(uniqueId));
        if (matches.Count() == 1) return matches.First();
        return null;
    }

I can fix this by changing the constructor to call LoadData().Wait, but this means that the app locks the UI thread. What I believe I need is a method of getting the recovery code in GetGroup to wait until the data has loaded without locking the UI thread. Is this possible or advisable, and if so, how?

EDIT:

One or two people have suggested caching the task for LoadData(). This is an excellent idea, but the code inside GetGroup is called by the Page State Management section and therefore cannot be async. To get around this, I tried the following:

if (!DataLoaded)
{
    //dataLoading = await MyDataSource.LoadData();
    dataLoading.RunSynchronously();
}

But this gives me an error:

RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.

and

dataLoading.Wait()

just locks the UI.

Paul Michaels
  • 16,185
  • 43
  • 146
  • 269
  • Why don't you start the LoadData() in another thread? I can't tell if `LoadData()` is a custom method or not, If its code you wrote, I highly suspect thats where the problem is. – Security Hound Jul 18 '12 at 17:49
  • set a bool before the call to LoadData; IsLoading = true; When the asynch load is complete set IsLoading = false; In you GetGroup check for IsLoading == true; and if so wait. – paparazzo Jul 18 '12 at 17:53
  • @Blam - that was my thinking. But how to wait without locking the UI thread? – Paul Michaels Jul 18 '12 at 18:49
  • @pm_2: if you can get/store an awaitable, then you can let await do this for you. It lets GetGroup have code that works regardless of whether the load has finished or not – James Manning Jul 18 '12 at 19:43
  • Not related to your question, but do you really want to return `null` if you find more than one match? I think an exception would make more sense and in that case, you can simplify your code by using `SingleOrDefault()`. – svick Jul 18 '12 at 19:53
  • OK see my attempt at an answer as I do something kind of like this but it is WPF – paparazzo Jul 18 '12 at 20:19

5 Answers5

5

I think that this sounds like the best option would be if you made the constructor async. But since that's not possible, what you can do instead is to create an async factory method for MyDataSource:

private MyDataSource()
{
    DataLoaded = false;
}

public static async Task<MyDataSource> Create()
{
    var source = new MyDataSource();
    await source.LoadData();
    return source;
}

And then initialize _myDataSource using await:

_myDataSource = await MyDataSource.Create();

If, for some reason, you can't do that, you can store the Task returned by the factory method and wait for it in GetGroup():

_myDataSourceTask = MyDataSource.Create();

…

public static async Task<MyDataGroup> GetGroup(string uniqueId)
{
    var myDataSource = await _myDataSourceTask;

    // Simple linear search is acceptable for small data sets
    var matches = myDataSource.AllGroups.Where(group => group.UniqueId == uniqueId);
    if (matches.Count() == 1) return matches.First();
    return null;
}
Community
  • 1
  • 1
svick
  • 236,525
  • 50
  • 385
  • 514
  • Unfortunately this doesn't work in practice. The reason being that `GetGroup` is called by the function that restores the state of the app (`LoadState`) and therefore cannot be made Async (GetGroup cannot be async). – Paul Michaels Jul 19 '12 at 19:53
  • You can still start an async operation from non-async method. You just won't wait until it finishes (unless you want to block). – svick Jul 19 '12 at 19:57
  • See edits above - obviously in this example, if I don't wait for it to finish then I end up in the same situation that I started in – Paul Michaels Jul 19 '12 at 20:02
  • Well, if you can't wait asynchronously and don't want to wait synchronously, then that means you'll end up with unfinished `Task`. I don't know WinRT enough to help you further. – svick Jul 19 '12 at 20:50
  • Sorry, my last comment was totally wrong (if I could down vote it then I would). I was under the incorrect impression that it wasn't possible to override a synchronous method with an async - but it is and this now works fine - thanks – Paul Michaels Jul 20 '12 at 18:38
1

If LoadData is async, then store whatever the awaitable is (or make a new one) and expose that (for instance, like a Task) then GetGroup can be marked async and can do var data = await _myDataSource.LoadTask or whatever

James Manning
  • 13,429
  • 2
  • 40
  • 64
1

I think Svick is the closest to answering the question because of his use of Tasks. Whether you return a Task on the GetGroup method or you return a task on a LoadAsync method probably doesn't matter. What DOES matter is that you capture that task and refer to it later on resume.

The Task class is documented here and you'll notice it has properties like IsCanceled, IsCompleted and IsFaulted. When your constructor kicks off the LoadAsync method you could save the Task it returns as a class-level variable. Later, when your resume code starts, you can check to see whether the Task is still running (i.e. not completed and not faulted). If the Task has completed, you can run your resume code right away. If it hasn't completed yet, you can use Task.ContinueWith to schedule your resume code to be run when the task completes.

Jared Bienz - MSFT
  • 3,550
  • 17
  • 21
0

I did not check out Windows 8 yet, but I assume it works similar to Windows Phone, assuming you use Silverlight (or WPF) to develop your app, this is not clear to me from your question. In case you use Silverlight:

You need to use the INotifyPropertyChanged interface, and ObservableCollection-s to be able to bind your data to the UI. That way everytime you change your data, the UI will be notified about the changes, and bindings will refresh.

Robert
  • 1,658
  • 16
  • 26
  • 1
    The bindings are working fine. As stated in the question, this question relates to the sequence of events following a suspend & terminate. – Paul Michaels Jul 18 '12 at 18:50
0

MyDataGroup a pubic property that implements iNotifyPropertyChanged.

UniqueID a public property. When UniqueID changes set MyDataGroup = null then call a BackGroundWorker to var mathces = but delay that if LoadData(); is working. Since this is in the background the delay will not tie up the UI. In the callback set MyDataGroup and the UI will get notified. Make sure you backgroundworker is cancelable as you will want to cancel it when UniqueID is changed (if it is running).

I do this in WPF so if it does not translate to Metro sorry and I will delete.

paparazzo
  • 44,497
  • 23
  • 105
  • 176