【问题标题】:How to use async method in DelegateCommand如何在 DelegateCommand 中使用异步方法
【发布时间】:2017-08-27 07:07:54
【问题描述】:

我想在 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
}

【问题讨论】:

  • MyJobAsync 必须是 async 本身,如果你要去 await 里面的东西,但除此之外,一切看起来都不错
  • @Haukinger 是的,你说得对,我忘了写它,我编辑了我的问题。谢谢
  • @Haukinger 这真的是异步的吗?换句话说,如果我在 MyJobAsync 中有昂贵的性能成本代码,UI 线程不会阻塞和流畅运行?
  • 我建议从这里开始:msdn.microsoft.com/en-us/magazine/dn630647.aspx 大部分流行的 MVVM 框架都是开源的,你可以查看它们是如何解决问题的。
  • 如果你使用异步 api,它就是异步的。如果你必须做 cpu-work,请使用 task.run

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


【解决方案1】:

看看这个链接如果你正在使用 Prism Libraryhttps://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)

希望对你有帮助。

【讨论】:

    【解决方案2】:

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

    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
    }
    

    【讨论】:

      【解决方案3】:

      可以直接使用async void。但是,根据我的经验,有几点说明...

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

      如果你真的确实需要一个异步命令(这种情况比较少见),你可以直接使用async void 或者构建一个异步命令类型,如我在我在async MVVM commmands 上的文章。我也有 types to support this,但这些 API 的变化更大。

      如果您选择直接使用async void

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

      【讨论】:

        【解决方案4】:

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

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

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

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

        【讨论】:

          【解决方案5】:

          如前所述,使用委托命令处理异步代码的方法是使用async void。对此进行了很多讨论,远远超出了 Prism 或 Xamarin Forms。底线是 ICommand,Xamarin Forms Command 和 Prism DelegateCommand 都受到 ICommandvoid Execute(object obj) 的限制。如果您想了解更多信息,我建议您阅读 Brian Lagunas 的博客,解释为什么 DelegateCommand.FromAsync handler is obsolete

          通常,通过更新代码可以很容易地处理大多数问题。例如。我经常听到有人抱怨 Exceptions 是 FromAsync 是必要的“原因”,只是在他们的代码中看到他们从未尝试过捕获。因为async void 是一劳永逸,我听到的另一个抱怨是一个命令可以执行两次。这也很容易用DelegateCommandsObservesPropertyObservesCanExecute 解决。

          【讨论】:

          • 为什么 MVVM 框架或 Xamarin.Forms 不提供 IAsyncCommand 或 DelegateAsyncCommand 等工具?完全没用吗?
          • @sorosh_sabz:IAsyncCommand 的主要目的是单元测试。直接使用 async void(不暴露 async Task 等效项)会妨碍对 VM 进行简单的单元测试。
          【解决方案6】:
          public DelegateCommand MyCommand => new DelegateCommand(MyMethod);
          
          private async void MyMethod()
          {
          
          }
          

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

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

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

          【讨论】:

          • 为什么我必须使用Device.BeginOnMainThread?我之前认为 await 捕获父上下文(我认为视图模型中的上下文是 UI 上下文)并使用此上下文运行 continue 代码块,那么为什么我不能直接在 async 方法中使用 UI 元素?
          【解决方案7】:
          public ICommand MyCommand{get;set;}
          
          //constructor
          public ctor()
          {
              MyCommand = new Xamarin.Forms.Command(CmdDoTheJob);
          }
          
          public async void DoTheJob()
          {
              await TheMethod();
          }
          

          【讨论】:

          • 不,这不是我的答案,我想使用棱镜框架。不过我感谢你的回答。
          猜你喜欢
          • 1970-01-01
          • 2013-03-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-25
          相关资源
          最近更新 更多