如何在DelegateCommand中使用异步方法

问题描述 投票:0回答:7

我想将异步方法链接到 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
}
xamarin mvvm xamarin.forms async-await prism
7个回答
18
投票

可以直接使用

async void
。然而,根据我的经验,有一些笔记...

代码的结构是:启动异步操作,然后用结果更新 UI。对我来说,这意味着您会更好地使用

NotifyTask<T>
异步 数据绑定 方法,而不是 命令。请参阅我的 async MVVM 数据绑定文章 ,了解有关
NotifyTask<T>
背后设计的更多信息(但请注意,最新代码 有错误修复和其他增强功能)。

如果您确实需要异步命令(这种情况很少见),您可以直接使用async void

或构建异步命令类型,如我在关于
异步MVVM命令的文章中所述。我也有类型来支持这一点,但这些 API 正在不断变化。

如果您选择直接使用

async void

    考虑将您的
  • async Task
    逻辑公开,或者至少可供您的单元测试访问。
  • 不要忘记正确处理异常情况。就像普通的
  • DelegateTask
     一样,您的代表的任何异常都必须得到正确处理。

7
投票
只需查看此链接

如果您使用 Prism 库https://docs.prismlibrary.com/docs/commands/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)
希望有帮助。


6
投票
正如已经提到的,使用委托命令处理异步代码的方法是使用

async void

。对此已有很多讨论,而不仅仅是 Prism 或 Xamarin Forms。底线是 
ICommand
 Xamarin Forms 
Command
 和 Prism 
DelegateCommand
 都受到 
ICommand
void Execute(object obj)
 的限制。如果您想获得更多相关信息,我鼓励您阅读 Brian Lagunas 的博客,解释为什么 
DelegateCommand.FromAsync
 处理程序已过时

通常,通过更新代码可以很容易地解决大多数问题。例如。我经常听到抱怨

Exceptions

 是 FromAsync 必要的“原因”,结果却在他们的代码中看到他们从未有过 try catch。因为 
async void
 是即发即忘,我听到的另一个抱怨是一个命令可能执行两次。这也可以通过 
DelegateCommands
 
ObservesProperty
ObservesCanExecute
 轻松解决。 


3
投票
我认为从同步执行的方法(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”方法几乎需要知道其失败的任务是如何传播的:

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 }
    

1
投票

UI 线程是否运行 DelegateCommand 且后台线程是否运行等待表达式?

是的,UI 线程运行

DelegateCommand

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

UI 线程是否运行 DelegateCommand

并且后台线程运行等待表达式?

“await 表达式”是否在后台线程、前台线程、线程池线程或其他线程上运行取决于您调用的 api。例如,您可以使用

Task.Run

 将 cpu 密集型工作推送到线程池,或者您可以使用 
Stream.ReadAsync
 等方法等待 i/o 操作而不使用任何线程
    


-1
投票
public ICommand MyCommand{get;set;} //constructor public ctor() { MyCommand = new Xamarin.Forms.Command(CmdDoTheJob); } public async void DoTheJob() { await TheMethod(); }
    

-1
投票
public DelegateCommand MyCommand => new DelegateCommand(MyMethod); private async void MyMethod() { }

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.