Consider the following two ways of setting an ICommand to perform an asynchronous task (in this case, using Xamarin.Forms.Command
, but I expect that's not critical):
Scenario 1: setting the command to an async lambda in which you await an async Task
method:
// Command definition
ToggleCheckedCommand = new Command(
execute: async () => { await ToggleCheckedAsync(); },
canExecute: () => !IsBusy);
// Method that is executed
private async Task ToggleCheckedAsync()
{
IsBusy = true;
await DoWork();
IsBusy = false;
}
Scenario 2: Setting the command to an async void
method:
// Command definition
ToggleCheckedCommand = new Command(
execute: ToggleCheckedAsync,
canExecute: () => !IsBusy);
// Method that is executed
private async void ToggleCheckedAsync()
{
IsBusy = true;
await DoWork();
IsBusy = false;
}
As long as one is never calling ToggleCheckedAsync
directly, are these two scenarios equivalent, or are there any gotchas with one compared to the other?
(I know async void
is generally considered a bad practice outside direct event handlers, but ToggleCheckedAsync
is logically an event handler, and the async lambda in scenario 1 is also AFAIK effectively async void
.)
As long as one is never calling ToggleCheckedAsync directly, are these two scenarios equivalent, or are there any gotchas with one compared to the other?
Either one is fine; they're equivalent approaches. async void
is appropriate here because ICommand.Execute
is logically an event handler. (And make no mistake: there is an async void
in both approaches: the lambda of the first example becomes async void
).
However, in my own code, this doesn't hold true:
As long as one is never calling ToggleCheckedAsync directly
In particular, with unit tests. Unit tests can execute your commands directly, and that includes being able to await
until they complete, and ICommand
doesn't satisfy this need.
So, I find it's useful to expose the async Task
methods. Or, if you want to get fancier, develop an IAsyncCommand
type with a Task ExecuteAsync
method and expose that from the ViewModel. Taking that design to its logical conclusion, you can end up with a full AsyncCommand
that hides the async void Execute
as an implementation detail.