Reputation: 24592
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
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
Reputation: 11
To instance AsyncCommand with a parameter, is correct this approach:
this.SaveCommand = new AsyncCommand((o) => SaveCommandHandlerAsync (o));
or is need
Upvotes: 1
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
Reputation: 905
You can try something like this:
(resetButtonClickedCommand = new Command(async () => await SomeMethod()));
async Task SomeMethod()
{
// do stuff
}
Upvotes: 27
Reputation: 41
you can write this also:-
(resetButtonClickedCommand = new Command(DoSomething));
async void DoSomething()
{
// do something
}
Note :- It shows warning at SomeMethod.
Upvotes: -3