1

I have a DialogViewModel class with async Task LoadData() method. This method loads data asynchronously and shows this dialog, which notifies user about loading. Here is the code:

try
{
    var dialog = new DialogViewModel();
    var loadTask = dialog.LoadData();
    WindowManager.ShowDialog(dialog);
    await loadTask;
}
catch (Exception ex)
{
    Logger.Error("Error in DialogViewModel", ex);
    // Notify user about the error
}

When LoadData throws an exception, it isn't handled until user exits the dialog. It happens because exception is handled when calling await, and it's not happening until WindowManager.ShowDialog(dialog) completes.

What is the correct way to show a dialog with async loading? I've tried this ways:

  1. Call LoadData() in OnShow(), constructor or similar. But this won't work if I'll need to show this dialog without any data
  2. Call await LoadData() before showing the dialog. This way user have to wait for data to load before actually seeing the window, but I want the window to show up instantly with a loading indicator.
STiLeTT
  • 1,023
  • 10
  • 23
  • http://stackoverflow.com/questions/13239306/how-to-continue-executing-code-after-calling-showdialog – Yuval Itzchakov Oct 30 '14 at 15:09
  • Unfortunately, the answers in this question aren't suitable for me. I have already wrote about `Show()`. I guess need to call 'LoadData()' from outside because only the caller determines whether to load data or not. As for 'BackgroundWorker' - I hope that there is a way to do this with async-await... – STiLeTT Oct 30 '14 at 15:38
  • Where does `WindowManager.ShowDialog()` come from? Is it Caliburn Micro? – svick Oct 31 '14 at 15:36
  • Yes, but it doesn't matter: it can be any `ShowDialog()` implementation, e.g. view injection. – STiLeTT Oct 31 '14 at 15:41

1 Answers1

0

Why is there an explicit public LoadData method?

If this has to happen then do it inside the constructor asynchronously using Task<T> with a ContinueWith to process any exception generated by checking the IsFaultedproperty on the returned task.

This would address both issues you've highlighted.

A very simple example is shown below, obivously you're implementation will be more complicated.

public class DialogViewModel
{
    private Task _task;

    public DialogViewModel()
    {
        var context = TaskScheduler.FromCurrentSynchronizationContext();

        _task = Task.Factory.StartNew(() =>
            {
                var data = GetDataCollection();
                return data;
            })
            .ContinueWith(t =>
            {
                if (t.IsFaulted)
                {
                    HasErrored = true;
                    ErrorMessage = "It's borked!";
                }
                else
                {
                    Data = t.Result;
                }
            }, context);
    }

    public IEnumerable<string> Data { get; private set; }

    public bool HasErrored { get; private set; }

    public string ErrorMessage { get; private set; }

    private static IEnumerable<string> GetDataCollection()
    {
        return new List<string>()
        {
            "John",
            "Jack",
            "Steve"
        };
    }
}

Or if you don't want to use Task<T> explicitly and want to use async\await functionality you could use a slightly different approach because you can't use async\await with a class constructor:

public class DialogViewModel
{
    public IEnumerable<string> Data { get; private set; }

    public bool HasErrored { get; private set; }

    public string ErrorMessage { get; private set; }

    async public static Task<DialogViewModel> BuildViewModelAsync()
    {
        try
        {
            var data = await GetDataCollection();
            return new DialogViewModel(data);
        }
        catch (Exception)
        {
            return new DialogViewModel("Failed!");
        }
    }

    private DialogViewModel(IEnumerable<string> data)
    {
        Data = data;
    }

    private DialogViewModel(string errorMessage)
    {
        HasErrored = true;
        ErrorMessage = errorMessage;
    }

    private async static Task<IEnumerable<string>> GetDataCollection()
    {
        // do something async...
        return await Task.Factory.StartNew(() => new List<string>()
        {
            "John",
            "Jack",
            "Steve"
        });
    }
}
AwkwardCoder
  • 24,893
  • 27
  • 82
  • 152
  • But then `LoadData()` will always be called, but I may want to show this dialog without loading any data (as I described in first case). – STiLeTT Oct 30 '14 at 16:14
  • well, this is where i think you have an issue, either it should be called always or it should never be called. Because otherwise it is not obvious when it should be called... – AwkwardCoder Oct 30 '14 at 16:16