如何在DelegateCommand中使用async方法

时间:2017-04-02 10:34:07

标签: xamarin mvvm xamarin.forms async-await prism

我想将异步方法链接到Xamarin.Forms中的prism框架中的委托命令,我的问题是如何做到这一点?

以下解决方案是否正确?是否存在任何陷阱? (僵局,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
}

8 个答案:

答案 0 :(得分:10)

可以直接使用async void。但是,从我的经验中得到了一些注释......

代码的结构是:启动异步操作,然后使用结果更新UI。这意味着,对于异步数据绑定而不是命令采用NotifyTask<T>方法,您会得到更好的服务。有关NotifyTask<T>背后的设计的更多信息,请参阅我的async MVVM data binding article(但请注意latest code有错误修正和其他增强功能)。

如果你真的需要异步命令(这种情况要少得多),你可以直接使用async void或者像我一样构建异步命令类型我在async MVVM commmands的文章中描述。我也有types to support this,但这些API更不稳定。

如果您确实选择直接使用async void

  • 考虑将您的async Task逻辑设为公开,或者至少可以进行单元测试。
  • 不要忘记妥善处理异常。就像普通DelegateTask一样,必须正确处理代理中的任何异常。

答案 1 :(得分:2)

正如已经提到的,使用delegate命令处理异步代码的方法是使用async void。关于这一点已经有很多讨论,远远超出了Prism或Xamarin Forms。底线是ICommand Xamarin表单Command和Prism DelegateCommandICommand void Execute(object obj)的限制。如果您想获得更多相关信息,我建议您阅读Brian Lagunas的博客,解释为什么DelegateCommand.FromAsync handler is obsolete

通常,通过更新代码可以非常轻松地处理大多数问题。例如。我经常听到关于Exceptions的抱怨和#34;原因&#34;为什么FromAsync是必要的,只是在他们的代码中看到他们从来没有尝试过捕获。因为async void是火和忘记,我听到的另一个抱怨是命令可以执行两次。使用DelegateCommands ObservesPropertyObservesCanExecute也可轻松解决此问题。

答案 2 :(得分:1)

  

UI线程是否正在运行DelegateCommand 和后台线程运行await表达式?

是的,UI线程运行DelegateCommand。如果是async,它将运行到第一个await语句,然后恢复其常规UI线程工作。如果awaiter配置为捕获同步上下文(即,使用.ConfigureAwait(false)),则UI线程将继续在DelegateCommand之后运行await }。

  

UI线程是否正在运行DelegateCommand 和后台线程运行await表达式?

&#34;是否等待表达&#34;运行在后台线程,前台线程,线程池线程或任何取决于您调用的API。例如,您可以使用Task.Run将cpu绑定的工作推送到线程池,或者您可以等待i / o操作而不使用任何线程,例如Stream.ReadAsync

等方法

答案 3 :(得分:1)

我认为从同步执行的方法(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

我个人会避免&#34; async void&#34;尽一切代价。当操作完成并且错误处理变得棘手时,不可能从外部知道。关于后者,例如编写&#34;异步任务&#34;从&#34; async void&#34;调用的方法方法几乎需要知道它的失败任务是如何传播的:

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
}

答案 4 :(得分:1)

只需查看以下链接,如果您使用的是棱镜库https://prismlibrary.com/docs/commanding.html#implementing-a-task-based-delegatecommand

如果要将CommandParameter传递给DelegateCommand,请在DelegateCommand变量声明中使用此语法

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

在ViewModel的构造函数中,通过以下方式对其进行初始化:

MyCommand = new DelegateCommand<object>(HandleTap);

其中HandleTap被声明为

private async void HandleTap(object param)

希望有帮助。

答案 5 :(得分:0)

将构造函数中的代码更改为:

 MyCommand = new DelegateCommand(() => { MyJobASync()});  

并在您的方法中:

private async Task MyJobASync()
{
   // your method
}

答案 6 :(得分:-1)

public ICommand MyCommand{get;set;}

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

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

答案 7 :(得分:-1)

public DelegateCommand MyCommand => new DelegateCommand(MyMethod);

private async void MyMethod()
{

}

没有陷阱。异步方法中的void返回类型是为代理创建的。如果您想要更改在UI上反映的内容,请在此块中插入相关代码:

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

实际上,ICommand和DelegateCommand非常相似,所以上面的答案是完全正确的。