2

I have an application that involves a database. Previously, upon opening a window, I would query the database and use this to populate aspects of my view model. This worked reasonably well, but could create noticeable pauses when the data access took longer than expected.

The natural solution, of course, is to run the database query asynchronously and then populate the view model when that query completes. This isn't too hard, but it raises some interesting questions regarding error handling.

Previously, if something went wrong with the database query (a pretty big problem, granted), I would propagate the exception through the view model constructor, ultimately making it back up to the caller that wanted to open the window. It could then display an appropriate error and not actually open the window.

Now, however, the window opens right away, then populates later as the query completes. The question, now, is at what point should I check for an error in the background task? The window is already open, so the behavior needs to be different somehow, but what is a clean way to indicate the failure to the user and allow for graceful recovery/shutdown?

For reference, here is a snippet demonstrating the basic pattern:

    public ViewModel()
    {
        _initTask = InitAsync();
        //Now where do I check on the status of the init task?
    }

    private async Task InitAsync()
    {
        //Do stuff...
    }

    //....

    public void ShowWindow()
    {
        var vm = new ViewModel(); //Previously this could throw an exception that would prevent window from being shown
        _windowServices.Show(vm);
    }

One option I've considered is use an asynchronous factory method for constructing the ViewModel, allowing the entire thing to be constructed and initialized before attempting to display the window. This preserves the old approach of reporting errors before the window is ever opened. However, it gives up some of the UI responsiveness gained by this approach, which allows initial loading of the window to occur in parallel with the query and also allows me (in some cases) to update the UI in increments as each query completes, rather than having the UI compose itself all at once. It avoids locking up the UI thread, but it doesn't reduce the time before the user actually sees the window and can start interacting with it.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102

3 Answers3

1

Maybe use some kind of messaging/mediator between your viewmodel and underlying service?

Semi-pseudo code using MVVMLight

public ViewModel()
{
    Messenger.Default.Register<NotificationMessage<Exception>>(this, message =>
        {
            // Handle here
        });

    Task.Factory.StartNew(() => FetchData());
}

public async Task FetchData()
{
    // Some magic happens here
    try
    {
        Thread.Sleep(2000);
        throw new ArgumentException();
    }
    catch (Exception e)
    {
        Messenger.Default.Send(new NotificationMessage<Exception>(this, e, "Aw snap!"));
    }
}
Mikko Viitala
  • 8,344
  • 4
  • 37
  • 62
  • The main thing I'm struggling with, I think, is the proper user experience when the window fails to populate with data. If the window doesn't open in the first place, there is a direct error response to whatever action requested it to be opened. In this case, the window appears (making it seem initially as if the action was successful), but then it turns out partway through loading the UI contents that something is wrong. – Dan Bryant Feb 26 '14 at 21:14
  • Ok, maybe still apply the mediator but instead of notifying about exceptions, send message for each successfull "unit of work" (containing the data you just fetched) and populate UI that way. Then have some flag if there was a problem. No? – Mikko Viitala Feb 26 '14 at 21:20
1

I dealt with a similar problem here. I found it'd be best for me to raise an error event from inside the task, like this:

// ViewModel

public class TaskFailedEventArgs: EventArgs
{
    public Exception Exception { get; private set; }
    public bool Handled { get; set; }

    public TaskFailedEventArgs(Exception ex) { this.Exception = ex; }
}

public event EventHandler<TaskFailedEventArgs> TaskFailed = delegate { };

public ViewModel()
{
    this.TaskFailed += (s, e) =>
    {
        // handle it, e.g.: retry, report or set a property

        MessageBox.Show(e.Exception.Message);
        e.Handled = true;
    };

    _initTask = InitAsync();
    //Now where do I check on the status of the init task?
}

private async Task InitAsync()
{
    try
    {
        // do the async work
    }
    catch (Exception ex)
    {
        var args = new TaskFailedEventArgs(ex);
        this.TaskFailed(this, args);
        if (!args.Handled)
            throw;
    }
}

// application

public void ShowWindow()
{
    var vm = new ViewModel(); //Previously this could throw an exception that would prevent window from being shown

    _windowServices.Show(vm);
}

The window still shows up, but it should be displaying some kind of progress notifications (e.g. using IProgress<T> pattern), until the end of the operation (and the error info in case it failed).

Inside the error event handler, you may give the user an option to retry or exit the app gracefully, depending on your business logic.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
1

Stephen Cleary has a series of posts on his blog about Async OOP. In particular, about constructors.

Community
  • 1
  • 1
Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59