Alan2
Alan2

Reputation: 24592

How can I call an async command with a view model?

I have this code and I am wanting to move it into a view model:

resetButton.Clicked += async (sender, e) =>
{
   if (App.totalPhrasePoints < 100 || await App.phrasesPage.DisplayAlert(
                "Reset score",
                "You have " + App.totalPhrasePoints.ToString() + " points. Reset to 0 ? ", "Yes", "No"))
      App.DB.ResetPointsForSelectedPhrase(App.cfs);
};

I realize I will need to set up something like this:

In my XAML code;

<Button x:Name="resetButton" Text="Reset All Points to Zero" Command="{Binding ResetButtonClickedCommand}"/>

And in my C# code:

private ICommand resetButtonClickedCommand;

public ICommand ResetButtonClickedCommand
{
   get
   {
      return resetButtonClickedCommand ??
      (resetButtonClickedCommand = new Command(() =>
      {

      }));

    }

But how can I fit the async action into a command?

Upvotes: 12

Views: 17374

Answers (5)

ToolmakerSteve
ToolmakerSteve

Reputation: 21341

On further testing, this class may be overkill for most uses.

Despite the downvotes, chawala's answer works fine in my tests.

Importantly, the presence of async on the method declaration is sufficient to avoid blocking UI thread. Therefore, chawala's answer is "not broken"; does not merit those downvotes, imho.

To be clear: the explicit async => await answers are of course perfectly fine, without any issue. Use them if that gives you more confidence.

My answer was intended to make the call site cleaner. HOWEVER, maxc's first comment is correct: what I have done is no longer "identical" to the explicit async => await. So far, I haven't found any situation where that matters. Whether with or without async/await inside new Command, if you click a button several times quickly, all the clicks get queued. I've even tested with SomeMethod switching to a new page. I have yet to find any difference whatsoever to the explicit async/await. All answers on this page have identical results, in my tests.

async void works just as well as async Task, if you aren't using the Task result anyway, and you aren't adding any code to do something useful with any exceptions that occur during this method.

In this class code, see my comment "TBD: Consider adding exception-handling logic here.".

Or to put it another way: most devs are writing code where it makes no difference. If that's a problem, then its going to equally be a problem in their new Command(await () => async SomeMethod()); version.


Below is a convenience class. Using it simplifies combining commands with async.

If you have an async method like this (copied from accepted answer):

async Task SomeMethod()
{
    // do stuff
}

Without this class, using that async method in a Command looks like this (copied from accepted answer):

resetButtonClickedCommand = new Command(async () => await SomeMethod());

With the class, usage is streamlined:

resetButtonClickedCommand = new AsyncCommand(SomeMethod);

The result is equivalent to the slightly longer code line shown without using this class. Not a huge benefit, but its nice to have code that hides clutter, and gives a name to an often-used concept.


The benefit becomes more noticeable given a method that takes a parameter:

async Task SomeMethod(object param)
{
    // do stuff
}

Without class:

yourCommand = new Command(async (param) => await SomeMethod(param));

With class (same as the no-parameter case; compiler calls the appropriate constructor):

yourCommand = new AsyncCommand(SomeMethod);

Definition of class AsyncCommand:

using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;

namespace MyUtilities
{
    /// <summary>
    /// Simplifies using an "async" method as the implementor of a Command.
    /// Given "async Task SomeMethod() { ... }", replaces "yourCommand = new Command(async () => await SomeMethod());"
    /// with "yourCommand = new AsyncCommand(SomeMethod);".
    /// Also works for methods that take a parameter: Given "async Task SomeMethod(object param) { ... }",
    /// Usage: "yourCommand = new Command(async (param) => await SomeMethod(param));" again becomes "yourCommand = new AsyncCommand(SomeMethod);".
    /// </summary>
    public class AsyncCommand : ICommand
    {
        Func<object, Task> _execute;
        Func<object, bool> _canExecute;

        /// <summary>
        /// Use this constructor for commands that have a command parameter.
        /// </summary>
        /// <param name="execute"></param>
        /// <param name="canExecute"></param>
        /// <param name="notificationSource"></param>
        public AsyncCommand(Func<object,Task> execute, Func<object, bool> canExecute = null, INotifyPropertyChanged notificationSource = null)
        {
            _execute = execute;
            _canExecute = canExecute ?? (_ => true);
            if (notificationSource != null) 
            {
                notificationSource.PropertyChanged += (s, e) => RaiseCanExecuteChanged();   
            }
        }

        /// <summary>
        /// Use this constructor for commands that don't have a command parameter.
        /// </summary>
        public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null, INotifyPropertyChanged notificationSource = null)
            :this(_ => execute.Invoke(), _ => (canExecute ?? (() => true)).Invoke(), notificationSource)
        {
        }

        public bool CanExecute(object param = null) => _canExecute.Invoke(param);

        public Task ExecuteAsync(object param = null) => _execute.Invoke(param);

        public async void Execute(object param = null)
        {
            // TBD: Consider adding exception-handling logic here.
            // Without such logic, quoting https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
            // "With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started."
            await ExecuteAsync(param);
        }

        public event EventHandler CanExecuteChanged;

        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }

}

Re comments below about async void Execute. Both class Command and interface ICommand have method void Execute. Being compatible with those, means having the same method signature - so the usually recommended async Task MethodName() is not an option here. See links in my comments for discussion of the implications of having void here.

Upvotes: 3

To instance AsyncCommand with a parameter, is correct this approach:

this.SaveCommand = new AsyncCommand((o) => SaveCommandHandlerAsync (o));

or is need

Upvotes: 1

pixel
pixel

Reputation: 10587

And to expand on already provided answer, if you need to pass a parameter to the command, you can use something like

(resetButtonClickedCommand = new Command<object>(async (o) => await SomeMethod(o)));

async Task SomeMethod(object o)
{
    // do stuff with received object
}

You could replace object above by anything you want as well.

Upvotes: 11

MaticDiba
MaticDiba

Reputation: 905

You can try something like this:

(resetButtonClickedCommand = new Command(async () => await SomeMethod()));

async Task SomeMethod()
{
    // do stuff
}

Upvotes: 27

Kapil chawala
Kapil chawala

Reputation: 41

you can write this also:-

(resetButtonClickedCommand = new Command(DoSomething));

async void DoSomething()
{
    // do something
}

Note :- It shows warning at SomeMethod.

Upvotes: -3

Related Questions