25

I want to link async method to a delegate command in prism framework in Xamarin.Forms and my question is how to do it?

Is below solution correct? Is there exist any pitfall? (deadlock, UI slow or freezing, bad practices, ...)

{      // My view model constructor
       ... 
       MyCommand = new DelegateCommand(async () => await MyJobAsync());
       ...
}

private async Task MyJobAsync()
{
       ... // Some await calls
       ... // Some UI element changed such as binded Observable collections
}
sorosh_sabz
  • 2,356
  • 2
  • 32
  • 53
  • `MyJobAsync` needs to be `async` itself if you're going to `await` stuff inside, but besides that, everything looks good – Haukinger Apr 02 '17 at 10:56
  • @Haukinger yes you right, I forgot it to write it, I edit my question. thx – sorosh_sabz Apr 02 '17 at 10:59
  • @Haukinger is this really async? In another word if I have expensive performance cost codes in MyJobAsync, UI thread does not block and rum smoothly? – sorosh_sabz Apr 02 '17 at 11:12
  • I recommend to start from here: https://msdn.microsoft.com/en-us/magazine/dn630647.aspx Most of the popular MVVM frameworks are open source, you can check how they solve the problem. – EvZ Apr 02 '17 at 13:57
  • 1
    it's async if you use an async api. if you have to do cpu-work, use task.run – Haukinger Apr 02 '17 at 16:18
  • @sorosh_sabz are you asking for a code review or is there an actual problem to be solved. if the latter then provide a [mcve] that can be used to reproduce the problem and help reach a solution. – Nkosi Apr 02 '17 at 16:45
  • 1
    @Nkosi I got useful information about my question with these comments and answer and I do not asking for code review, I want to know how to work with `async` method in `command` fields in Prism and what is best practice exist around this subject. – sorosh_sabz Apr 02 '17 at 19:46
  • @Haukinger Is there a better solution ( or best practice ) for calling `async` method in Command? – sorosh_sabz Apr 02 '17 at 19:48
  • Is UI thread running `DelegateCommand` and background threads running await expression? – sorosh_sabz Apr 02 '17 at 19:58

7 Answers7

17

You can use async void directly. However, a few notes from my experience...

The structure of your code is: start asynchronous operation and then update UI with the results. This implies to me that you would be better served with a NotifyTask<T> kind of approach to asynchronous data binding, not commands. See my async MVVM data binding article for more about the design behind NotifyTask<T> (but note that the latest code has a bugfix and other enhancements).

If you really do need an asynchronous command (which is much more rare), you can use async void directly or build an async command type as I describe in my article on async MVVM commmands. I also have types to support this but the APIs for these are more in flux.

If you do choose to use async void directly:

  • Consider making your async Task logic public, or at least accessible to your unit tests.
  • Don't forget to handle exceptions properly. Just like a plain DelegateTask, any exceptions from your delegate must be properly handled.
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    "If you really do need an asynchronous command (which is much more rare)" - Why you think that using `async commands` is a rare case ? What if we make all commands `async` ? – isxaker Mar 17 '22 at 09:58
  • @isxaker: In my experience they're not common. Usually, a command will synchronously start an asynchronous operation which then (asynchronously) updates the UI. In this case, the command itself is actually synchronous, and just uses asynchronous data binding to represent the operation. – Stephen Cleary Mar 17 '22 at 12:26
  • This is sooo useful, any chance you could update the links on this answer? – Colin Nov 28 '22 at 11:09
6

Just have a look at this link if you're using Prism Library: https://prismlibrary.com/docs/commands/commanding.html#implementing-a-task-based-delegatecommand

In case you want to pass a CommandParameter to DelegateCommand, use in the DelegateCommand variable declaration this syntax

public DelegateCommand<object> MyCommand { get; set; }

In the constructor of the ViewModel initialize it this way:

MyCommand = new DelegateCommand<object>(HandleTap);

where HandleTap is declared as

private async void HandleTap(object param)

Hope it helps.

LiamJM
  • 172
  • 12
Mattia Ducci
  • 412
  • 6
  • 10
5

As has already been mentioned the way to handle async code with delegate command is to use async void. There has been a lot of discussion on this, far beyond just Prism or Xamarin Forms. The bottom line is that ICommand that both the Xamarin Forms Command and Prism DelegateCommand are limited by ICommand's void Execute(object obj). If you'd like to get more information on this I would encourage you to read the blog by Brian Lagunas explaining why DelegateCommand.FromAsync handler is obsolete.

Generally most concerns are handled very easily by updating the code. For example. I often hear complaints about Exceptions as "the reason" why FromAsync was necessary, only to see in their code they never had a try catch. Because async void is fire and forget, another complaint I've heard is that a command could execute twice. That also is easily fixed with DelegateCommands ObservesProperty and ObservesCanExecute.

Dan Siegel
  • 5,724
  • 2
  • 14
  • 28
  • Why MVVM frameworks or Xamarin.Forms does not provide some facilities like IAsyncCommand or DelegateAsyncCommand? Is totally useless? – sorosh_sabz Apr 02 '17 at 19:42
  • @sorosh_sabz: The primary purpose of `IAsyncCommand` is unit testing. Using `async void` directly (without exposing an `async Task` equivalent) prevents easy unit testing of the VM. – Stephen Cleary Apr 03 '17 at 14:09
3

I think the two main problems when calling an asynchronous method from one that executes synchronously (ICommand.Execute) are 1) denying to execute again while previous call is still running 2) handling of exceptions. Both can be tackled with an implementation like the following (prototype). This would be an async replacement for the DelegateCommand.

public sealed class AsyncDelegateCommand : ICommand
{
    private readonly Func<object, Task> func;
    private readonly Action<Exception> faultHandlerAction;
    private int callRunning = 0;

    // Pass in the async delegate (which takes an object parameter and returns a Task) 
    // and a delegate which handles exceptions
    public AsyncDelegateCommand(Func<object, Task> func, Action<Exception> faultHandlerAction)
    {
        this.func = func;
        this.faultHandlerAction = faultHandlerAction;
    }

    public bool CanExecute(object parameter)
    {
        return callRunning == 0;
    }

    public void Execute(object parameter)
    {
        // Replace value of callRunning with 1 if 0, otherwise return - (if already 1).
        // This ensures that there is only one running call at a time.
        if (Interlocked.CompareExchange(ref callRunning, 1, 0) == 1)
        {
            return;
        }
        OnCanExecuteChanged();
        func(parameter).ContinueWith((task, _) => ExecuteFinished(task), null, TaskContinuationOptions.ExecuteSynchronously);
    }

    private void ExecuteFinished(Task task)
    {
        // Replace value of callRunning with 0
        Interlocked.Exchange(ref callRunning, 0);
        // Call error handling if task has faulted
        if (task.IsFaulted)
        {
            faultHandlerAction(task.Exception);
        }
        OnCanExecuteChanged();
    }

    public event EventHandler CanExecuteChanged;

    private void OnCanExecuteChanged()
    {
        // Raising this event tells for example a button to display itself as "grayed out" while async operation is still running
        var handler = CanExecuteChanged;
        if (handler != null) handler(this, EventArgs.Empty);
    }
}

async void

I personally would avoid "async void" at all cost. It is impossible to know from the outside when the operation has finished and error handling becomes tricky. In regards to latter, for instance writing an "async Task" method which is called from an "async void" method almost needs to be aware of how its failing Task is propagated:

public async Task SomeLogic()
{
    var success = await SomeFurtherLogic();
    if (!success) 
    {
        throw new DomainException(..); // Normal thing to do
    }
}

And then someone writing on a different day:

public async void CommandHandler()
{
    await SomeLogic();  // Calling a method. Normal thing to do but can lead to an unobserved Task exception
}
Thomas Zeman
  • 890
  • 1
  • 7
  • 16
1

Is UI thread running DelegateCommand and background threads running await expression?

Yes, the UI thread runs the DelegateCommand. In case of an async one, it runs until the first await statement, and then resumes his regular UI thread work. If the awaiter is configured to capture the synchronization context (that is, you do not use .ConfigureAwait(false)) the UI thread will continue to run the DelegateCommand after the await.

Is UI thread running DelegateCommand and background threads running await expression?

Whether the "await expression" runs on a background thread, foreground thread, a threadpool thread or whatever depends on the api you call. For example, you can push cpu-bound work to the threadpool using Task.Run or you can wait for an i/o-operation without using any thread at all with methods like Stream.ReadAsync

Haukinger
  • 10,420
  • 2
  • 15
  • 28
-1
public ICommand MyCommand{get;set;}

//constructor
public ctor()
{
    MyCommand = new Xamarin.Forms.Command(CmdDoTheJob);
}

public async void DoTheJob()
{
    await TheMethod();
}
eakgul
  • 3,658
  • 21
  • 33
-1
public DelegateCommand MyCommand => new DelegateCommand(MyMethod);

private async void MyMethod()
{

}

There are no pitfalls. A void return type in async method was created especially for delegates. If you want to change something, that has reflected on UI, insert relevant code in this block:

Device.BeginOnMainThread(()=>
{
    your code;
});

Actually, ICommand and DelegateCommand pretty similar, so an above answer is quite right.

Yura Babiy
  • 515
  • 7
  • 22
  • 1
    Why I have to use `Device.BeginOnMainThread`? I had previously thought that await capture parent context ( I think context in view model is UI context ) and run continue block of code with this context so why I could not use UI element in `async` method directly? – sorosh_sabz Apr 02 '17 at 19:53