xamarin 如何在DelegateCommand中使用异步方法

vu8f3i0k  于 2022-12-07  发布在  其他
关注(0)|答案(7)|浏览(167)

我想在Xamarin.Forms中把async方法链接到prism框架中的一个delegate命令,我的问题是怎么做?
以下解决方案是否正确?是否存在任何缺陷?(死锁、UI缓慢或冻结、不良实践...)

{      // 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
}
db2dz4w8

db2dz4w81#

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.
tvokkenx

tvokkenx2#

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.

wljmcqd8

wljmcqd83#

正如已经提到的,用delegate命令处理异步代码的方法是使用async void,对此已有很多讨论,远远超出了仅仅是棱镜或Xamarin形式。底线是ICommandCommand和棱镜DelegateCommand都受到ICommandvoid Execute(object obj)的限制。如果你'I“我想了解更多有关这方面的信息,我鼓励您阅读Brian Lagunas的博客,其中解释了为什么DelegateCommand.FromAsync handler is obsolete
一般来说,大多数问题都可以通过更新代码来轻松解决。例如,我经常听到关于Exceptions的抱怨,因为Exceptions是FromAsync必不可少的“原因”,结果却发现他们的代码中从来没有try catch。因为async void是“发射后忘记,”我听到的另一个抱怨是一个命令可能执行两次。这个问题也很容易用DelegateCommandsObservesPropertyObservesCanExecute解决。

2cmtqfgy

2cmtqfgy4#

我认为从一个同步执行的方法(ICommand.Execute)调用一个异步方法时的两个主要问题是1)在前一个调用仍在运行时拒绝再次执行2)异常处理。这两个问题都可以通过如下的实现(原型)来解决。这将是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”。从外部不可能知道操作何时完成,错误处理变得棘手。关于后者,例如编写一个从“async void”方法调用的“async Task”方法,几乎需要知道它的失败Task是如何传播的:

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

然后有人在另一天写:

public async void CommandHandler()
{
    await SomeLogic();  // Calling a method. Normal thing to do but can lead to an unobserved Task exception
}
vfwfrxfs

vfwfrxfs5#

  • UI线程是否运行DelegateCommand*,后台线程是否运行await表达式?

是的,UI线程运行DelegateCommand。如果是async线程,它将运行到第一个await语句,然后恢复其常规UI线程工作。如果将等待器配置为捕获同步上下文(即,您 * 不 * 使用.ConfigureAwait(false)),则UI线程将在await之后继续运行DelegateCommand
UI线程是否运行DelegateCommand ,后台线程是否运行await表达式?
“await表达式”是在后台线程、前台线程、线程池线程还是其他线程上运行取决于您调用的api。例如,您可以使用Task.Run将CPU绑定的工作推送到线程池,或者您可以使用Stream.ReadAsync等方法等待i/o操作,而根本不使用任何线程

xoefb8l8

xoefb8l86#

public ICommand MyCommand{get;set;}

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

public async void DoTheJob()
{
    await TheMethod();
}
wgmfuz8q

wgmfuz8q7#

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.

相关问题