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

Xamarin 如何在DelegateCommand中使用异步方法,xamarin,mvvm,xamarin.forms,async-await,prism,Xamarin,Mvvm,Xamarin.forms,Async Await,Prism,我想将异步方法链接到Xamarin.Forms中prism框架中的委托命令,我的问题是如何做 下面的解决方案正确吗?有什么陷阱吗?(死锁、UI缓慢或冻结、不良做法等) 没有陷阱。异步方法中的void返回类型是专门为委托创建的。如果要更改反映在UI上的内容,请在此块中插入相关代码: Device.BeginOnMainThread(()=> { your code; }); 实际上,ICommand和DelegateCommand非常相似,所以上面的答案非常正确 如前所述,使用委托

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

下面的解决方案正确吗?有什么陷阱吗?(死锁、UI缓慢或冻结、不良做法等)

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

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

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

如前所述,使用委托命令处理异步代码的方法是使用
async void
。关于这一点,已经有很多讨论,远远超出了棱镜或Xamarin形式。底线是,
ICommand
Xamarin表单
命令
和Prism
DelegateCommand
都受到
ICommand
无效执行(对象obj)
的限制。如果你想得到更多关于这方面的信息,我鼓励你阅读Brian Lagunas的博客,解释原因

通常,通过更新代码可以很容易地处理大多数问题。例如我经常听到人们抱怨
异常
是FromAsync之所以必要的“原因”,只是在他们的代码中他们从来没有尝试过。因为
async void
是fire and forget,所以我听到的另一个抱怨是一个命令可能执行两次。这也可以通过
DelegateCommands
ObservesProperty
ObservesCanExecute
轻松解决

UI线程是否运行DelegateCommand,后台线程是否运行Wait表达式

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

UI线程是否运行DelegateCommand,后台线程是否运行Wait表达式

“等待表达式”是否在后台线程、前台线程、线程池线程或其他线程上运行取决于您调用的api。例如,您可以使用
Task.Run
将cpu绑定的工作推送到线程池,也可以使用
Stream等方法等待i/o操作,而不使用任何线程。ReadAsync
您可以直接使用
异步void
。然而,从我的经验中可以看出

代码的结构是:启动异步操作,然后用结果更新UI。这对我来说意味着,使用一种异步数据绑定的方法,而不是命令,会更好。有关
NotifyTask
背后的设计的更多信息,请参见my(但请注意,具有错误修复和其他增强功能)

如果您确实需要一个异步命令(这是非常罕见的),您可以直接使用
async void
,或者构建一个异步命令类型,就像我在上一篇文章中描述的那样。我也有,但这些API的变化更大

如果您选择直接使用
async void

  • 考虑将
    异步任务
    逻辑公开,或者至少让单元测试可以访问
  • 不要忘记正确处理异常。就像普通的
    DelegateTask
    一样,必须正确处理来自委托的任何异常

我认为从同步执行的异步方法(ICommand.Execute)调用异步方法时,两个主要问题是1)在上一次调用仍在运行时拒绝再次执行2)处理异常。这两个问题都可以通过下面的实现(原型)来解决。这将是DelegateCommand的异步替换

公共密封类AsyncDelegateCommand:ICommand
{
私有只读函数Func;
私有只读操作容错;
private int callRunning=0;
//传入异步委托(接受对象参数并返回任务)
//以及处理异常的委托
公共AsyncDelegateCommand(Func-Func,Action FaultHandleration)
{
this.func=func;
this.faultHandleration=faultHandleration;
}
公共布尔CanExecute(对象参数)
{
返回callRunning==0;
}
public void Execute(对象参数)
{
//如果为0,则将callRunning的值替换为1,否则返回-(如果已为1)。
//这样可以确保一次只有一个正在运行的调用。
if(联锁的比较交换(ref callRunning,1,0)==1)
{
返回;
}
OnCanExecuteChanged();
func(parameter).ContinueWith((task,)=>ExecuteFinished(task),null,TaskContinuationOptions.ExecuteSynchronously);
}
私有void ExecuteFinished(任务)
{
//将callRunning的值替换为0
联锁交换(参考callRunning,0);
//任务出现故障时调用错误处理
if(task.IsFaulted)
{
FaultHandleration(任务异常);
}
OnCanExecuteChanged();
}
公共事件处理程序CanExecuteChanged;
私有void OnCanExecuteChanged()
{
//例如,引发此事件会告诉按钮在异步操作仍在运行时显示为“灰色”
var handler=CanExecuteChanged;
if(handler!=null)handler(this,EventArgs.Empty);
}
}
异步无效

我个人会不惜一切代价避免“异步无效”。不可能从外部知道操作何时完成,错误处理变得棘手。对于后者,例如,编写一个“异步任务”方法,该方法是从一个“异步void”方法调用的
public ICommand MyCommand{get;set;}

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

public async void DoTheJob()
{
    await TheMethod();
}
public DelegateCommand MyCommand => new DelegateCommand(MyMethod);

private async void MyMethod()
{

}
Device.BeginOnMainThread(()=>
{
    your code;
});
 MyCommand = new DelegateCommand(() => { MyJobASync()});  
private async Task MyJobASync()
{
   // your method
}
public DelegateCommand<object> MyCommand { get; set; }
MyCommand = new DelegateCommand<object>(HandleTap);
private async void HandleTap(object param)