Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async/await in MVVM without Void methods

I want to use async/await on my windows phone 8 MVVM project and I'm struggling to find a good way to implement my ICommands using this api. I've been reading a few articles about the subject and I bumped into this one from MSDN below, which states that I have to avoid async voids since it gets difficult to catch unhandled exceptions: http://msdn.microsoft.com/en-us/magazine/jj991977.aspx In another question I asked about the subject, someone also said that I shouldn't use async voids. Unless with events.

But the problem is that all examples I can find on the internet use async voids. This two articles I found are examples: http://richnewman.wordpress.com/2012/12/03/tutorial-asynchronous-programming-async-and-await-for-beginners/ and http://blog.mycupof.net/2012/08/23/mvvm-asyncdelegatecommand-what-asyncawait-can-do-for-uidevelopment/

The last one is an implementation of ICommand using async/await, but it also uses async voids. I'm trying to come up with a solution for this, so I wrote this implementation of ICommand based on the RelayCommand:

public delegate Task AsyncAction();

public class RelayCommandAsync : ICommand
{
    private AsyncAction _handler;
    public RelayCommandAsync(AsyncAction handler)
    {
        _handler = handler;
    }

    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            if (value != _isEnabled)
            {
                _isEnabled = value;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, EventArgs.Empty);
                }
            }
        }
    }

    public bool CanExecute(object parameter)
    {
        return IsEnabled;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        ExecuteAsync();
    }

    private Task ExecuteAsync()
    {
        return _handler();
    }
}

And I'm trying to use it like this: in the constructor:

saveCommand = new RelayCommandAsync(SaveSourceAsync);

then:

private async Task SaveSourceAsync()
{
    await Task.Run(() => { Save(); });
}

private void Save()
{
    // Slow operation
}

The problem is that I'm not feeling comfortable with this and any other implementation as I don't know which is the best and optimal.

Can anyone give some light on how I should use it, preferably with MVVM?

like image 999
Eric.M Avatar asked May 31 '13 01:05

Eric.M


1 Answers

In the referenced article, I did point out that ICommand.Execute is practically an event handler, so it would be considered an exception from the "avoid async void" guideline:

To summarize this first guideline, you should prefer async Task to async void... The exception to this guideline is asynchronous event handlers, which must return void. This exception includes methods that are logically event handlers even if they’re not literally event handlers (for example, ICommand.Execute implementations).

Regarding your ICommand implementation, it actually introduces a flaw by not using async void: the ICommand.Execute implementation will discard the Task without observing its exceptions. So that implementation will ignore any exceptions raised by the async delegate.

In contrast, the blog post you linked to has an async void ICommand.Execute which awaits the Task, allowing the exception to propagate to the UI synchronization context. Which - in this case - is the desired behavior because it's the same behavior you get when a synchronous ICommand.Execute raises an exception.

If you have the inclination, I'd like you to try out an ICommand or two that I've written for possible future inclusion in my AsyncEx library. The first one is a simple command very similar to the one in the blog you posted. The second one is a much more complete "asynchronous command" implementation including cancellation, progress reporting, and automatic management of CanExecute. I'd appreciate any feedback.

like image 197
Stephen Cleary Avatar answered Sep 26 '22 16:09

Stephen Cleary