Rejected designs for async loading


Previously, we talked about handling lost devices, putting CanvasControl in charge of resource creation policy, and how async loading crashed our party. We were left looking around for ways to make async loading work…

One option we considered would be to use the event args to allow the CreateResources to say that it is done. Then CanvasControl could know not to raise the Draw event until this done flag is set:

async void Canvas_CreateResources(
    CanvasControl sender,
    CanvasCreateResourcesEventArgs args)
{
    bitmap1 = await CanvasBitmap.LoadAsync(sender, "hello.png");
    bitmap2 = await CanvasBitmap.LoadAsync(sender, "goodbye.png");
    args.Done = true;  
}

Does this fix our bugs?

  1. It’s possible to forget to call “Invalidate”: fixed! The property setter can trigger Invalidate for us.
  2. “isLoaded” flag is repetitive and possible to get wrong: nearly fixed!
  3. Exceptions thrown after event handler returns: not fixed!
  4. CreateResources event might fire before previous one completes: fixed!

Two out of four ain’t bad. Even if issue #3 were fixed, the bigger problem with this is that even non-async CreateResources handlers will need to set args.Done. That’s no good! Adding an empty CreateResources handler would prevent any drawing from happening. There needs to be some way to register the intent to call Done. Something like:

async void Canvas_CreateResources(
    CanvasControl sender,
    CanvasCreateResourcesEventArgs args)
{
    var doneTracker = args.GetDoneTracker();
    bitmap1 = await CanvasBitmap.LoadAsync(sender, "hello.png");
    bitmap2 = await CanvasBitmap.LoadAsync(sender, "goodbye.png");
    doneTracker.Done = true;
}

But we still haven’t dealt with the problem of exceptions thrown after the event handler has returned. What we really need is a way to actually track the async operation itself.

IAsyncAction

WinRT has IAsyncAction as a way to do this, and .NET has Tasks. Everything would be great if we could write this:

async Task Canvas_CreateResources(
    CanvasControl sender, 
    CanvasCreateResourcesEventArgs args)
{
    bitmap1 = await CanvasBitmap.LoadAsync(sender, "hello.png");
    bitmap2 = await CanvasBitmap.LoadAsync(sender, "goodbye.png");
}

Unfortunately, this just isn’t supported:

  1. Events can’t return values.
  2. Tasks don’t auto-convert to IAsyncActions

What we need is to have some way to allow the app to give an IAsyncAction to CanvasControl. Our first pass on this was to have a delegate that returns an IAsyncAction:

public MainPage()
{
    this.InitializeComponent();
    canvas.CreateResourcesAsync =
        sender => CreateResourcesAsync(sender).AsAsyncAction();
}

async Task CreateResourcesAsync(CanvasControl sender)
{
    bitmap1 = await CanvasBitmap.LoadAsync(sender, "hello.png");
    bitmap2 = await CanvasBitmap.LoadAsync(sender, "goodbye.png");
}

The great thing here is that the async/await mechanism will capture any exceptions that are thrown and report them through IAsyncAction.ErrorCode. This puts CanvasControl back into control of error handling. Even better, now that we have an IAsyncAction object, CanvasControl is able to call Cancel() on it if it knows that the device is lost and it is going to need to reschedule CreateResources anyway.

We got as far as a complete implementation of this scheme but ultimately rejected it because:

  • it’s confusing to have both the CreateResources event and a CreateResourcesAsync property
  • the lambda notation is a bit too magic
  • using raw delegates (rather than events) is very unusual

We’re nearly there! In the next part we’ll look at the solution we landed on…

 


Comments (1)

  1. KooKiz says:

    That's a very interesting series of articles, please keep going!

Skip to main content