如何在 DelegateCommand 中使用异步方法

How to use async method in DelegateCommand

提问人:sorosh_sabz 提问时间:4/2/2017 最后编辑:sorosh_sabz 更新时间:10/29/2022 访问量:25240

问:

我想将异步方法链接到 Xamarin.Forms 中棱镜框架中的委托命令,我的问题是该怎么做?

以下解决方案是否正确?有什么陷阱吗?(死锁、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 棱镜

评论

0赞 Haukinger 4/2/2017
MyJobAsync如果你要塞进去,就需要成为它自己,但除此之外,一切看起来都很好asyncawait
0赞 sorosh_sabz 4/2/2017
@Haukinger是的,你说得对,我忘了写它,我编辑了我的问题。感谢
0赞 sorosh_sabz 4/2/2017
这真的是异步@Haukinger?换句话说,如果我在 MyJobAsync 中有昂贵的性能成本代码,UI 线程不会阻塞和顺利朗姆酒?
1赞 Haukinger 4/3/2017
如果使用异步 API,则为异步。如果必须执行 cpu-work,请使用 task.run
1赞 sorosh_sabz 4/3/2017
@Nkosi我通过这些评论和答案获得了有关我的问题的有用信息,并且我不要求代码审查,我想知道如何使用 Prism 中字段中的方法以及围绕该主题存在的最佳实践。asynccommand

答:

-1赞 eakgul 4/2/2017 #1
public ICommand MyCommand{get;set;}

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

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

评论

0赞 sorosh_sabz 4/2/2017
不,这不是我的答案,我想使用棱镜框架。但是,我感谢您的回答。
-1赞 Yura Babiy 4/2/2017 #2
public DelegateCommand MyCommand => new DelegateCommand(MyMethod);

private async void MyMethod()
{

}

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

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

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

评论

1赞 sorosh_sabz 4/3/2017
为什么我必须使用?我以前认为等待捕获父上下文(我认为视图模型中的上下文是UI上下文)并使用此上下文运行继续代码块,那么为什么我不能直接在方法中使用UI元素?Device.BeginOnMainThreadasync
5赞 Dan Siegel 4/3/2017 #3

如前所述,使用 delegate 命令处理异步代码的方法是使用 .关于这一点的讨论已经很多了,远远超出了 Prism 或 Xamarin Forms。底线是 Xamarin Forms 和 Prism 都受到 的限制。如果您想获得有关此内容的更多信息,我鼓励您阅读 Brian Lagunas 的博客,解释为什么 DelegateCommand.FromAsync 处理程序已过时async voidICommandCommandDelegateCommandICommandvoid Execute(object obj)

通常,通过更新代码可以很容易地处理大多数问题。例如。我经常听到抱怨为什么需要 FromAsync 作为“原因”,只是在他们的代码中看到他们从未尝试过。因为是一劳永逸,我听到的另一个抱怨是命令可以执行两次。这也很容易用 和 修复。Exceptionsasync voidDelegateCommandsObservesPropertyObservesCanExecute

评论

0赞 sorosh_sabz 4/3/2017
为什么 MVVM 框架或 Xamarin.Forms 不提供某些工具,如 IAsyncCommand 或 DelegateAsyncCommand?完全没用吗?
0赞 Stephen Cleary 4/3/2017
@sorosh_sabz:主要目的是单元测试。直接使用(不公开等效项)可防止对 VM 进行简单的单元测试。IAsyncCommandasync voidasync Task
1赞 Haukinger 4/3/2017 #4

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

是的,UI 线程运行 .如果是 one,它会一直运行到第一个语句,然后恢复他的常规 UI 线程工作。如果 awaiter 配置为捕获同步上下文(即,您使用 ),则 UI 线程将在 .DelegateCommandasyncawait.ConfigureAwait(false)DelegateCommandawait

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

“await 表达式”是否在后台线程、前台线程、线程池线程或其他线程上运行取决于您调用的 API。例如,您可以使用以下方法将 cpu 绑定的工作推送到线程池,或者您可以等待 i/o 操作而不使用任何线程Task.RunStream.ReadAsync

17赞 Stephen Cleary 4/3/2017 #5

您可以直接使用。但是,我的经验中的一些笔记......async void

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

如果你确实需要一个异步命令(这种情况要少得多),你可以直接使用或构建一个异步命令类型,正如我在关于异步MVVM通信的文章中所描述的那样。我也有类型来支持这一点,但这些 API 在不断变化。async void

如果您选择直接使用:async void

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

评论

1赞 isxaker 3/17/2022
“如果你真的需要一个异步命令(这要少得多)” - 为什么你认为使用是一种罕见的情况?如果我们发出所有命令怎么办?async commandsasync
0赞 Stephen Cleary 3/17/2022
@isxaker:根据我的经验,它们并不常见。通常,命令将同步启动异步操作,然后(异步)更新 UI。在这种情况下,命令本身实际上是同步的,只是使用异步数据绑定来表示操作。
0赞 Colin 11/28/2022
这太有用了,你有机会更新这个答案的链接吗?
3赞 Thomas Zeman 4/3/2017 #6

我认为从同步执行的异步方法(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 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
}
6赞 Mattia Ducci 5/13/2020 #7

如果您使用的是 Prism Library,请查看此链接: https://prismlibrary.com/docs/commands/commanding.html#implementing-a-task-based-delegatecommand

如果你想把一个 传递给 ,在变量声明中使用这个语法CommandParameterDelegateCommandDelegateCommand

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

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

MyCommand = new DelegateCommand<object>(HandleTap);

其中声明为HandleTap

private async void HandleTap(object param)

希望对你有所帮助。