【问题标题】:How can I use async in an mvvmcross view model?如何在 mvvmcross 视图模型中使用异步?
【发布时间】:2013-06-15 17:51:24
【问题描述】:

我在 mvvmcross 视图模型中有一个长时间运行的进程,并希望使其异步 (http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx)。

Xamarin 的 beta 通道目前支持 async 关键字。

以下是我当前如何实现异步的示例。 IsBusy 标志可以绑定到 UI 元素并显示加载消息。

这是正确的方法吗?

public class MyModel: MvxViewModel
{
    private readonly IMyService _myService;
    private bool _isBusy;

    public bool IsBusy
    {
        get { return _isBusy; }
        set { _isBusy = value; RaisePropertyChanged(() => IsBusy); ; }
    }

    public ICommand MyCommand
    {
        get
        {
            return new MvxCommand(DoMyCommand);
        }
    }

    public MyModel(IMyService myService)
    {
        _myService = myService;
    }

    public async void DoMyCommand()
    {
        IsBusy = true;
        await Task.Factory.StartNew(() =>
            {
                _myService.LongRunningProcess();
            });
        IsBusy = false;
    }

}

【问题讨论】:

    标签: c# async-await xamarin mvvmcross


    【解决方案1】:

    你应该避免async void。在处理ICommand 时,确实需要使用async void,但应尽量减少其范围。

    此修改后的代码将您的操作公开为 async Task,它是可单元测试的并且可以从代码的其他部分使用:

    public class MyModel: MvxViewModel
    {
      private readonly IMyService _myService;
      private bool _isBusy;
    
      public bool IsBusy
      {
        get { return _isBusy; }
        set { _isBusy = value; RaisePropertyChanged(() => IsBusy); ; }
      }
    
      public ICommand MyCommand
      {
        get
        {
          return new MvxCommand(async () => await DoMyCommand());
        }
      }
    
      public MyModel(IMyService myService)
      {
        _myService = myService;
      }
    
      public async Task DoMyCommand()
      {
        IsBusy = true;
        await Task.Run(() =>
        {
          _myService.LongRunningProcess();
        });
        IsBusy = false;
      }
    }
    

    您可以使用IsBusy;这是异步 UI 中的一种常见方法。

    我确实将Task.Factory.StartNew 更改为Task.RunTask.Runasync 代码中首选reasons described by Stephen Toub

    【讨论】:

    • 自从我发布了这个我已经删除了 IsBusy 标志并添加了对 IAlertsService 的调用。这有助于集中加载/警报逻辑并将其从各个视图中删除。
    • 这很好用:return new MvxCommand(() => DoMyCommand());
    • async lambda 为您提供略有不同的错误处理。使用() => DoMyCommand(),来自DoMyCommand 的任何异常都会被默默吞下。使用async () => await DoMyCommand(),来自DoMyCommand 的任何异常都被视为未处理的异常。
    • @ChrisKoiak,您能否在博客文章或文章中举例说明您如何实现和使用 IAlertService,或者只是在这里进行概念性解释?
    • @Vackup:我倾向于遵循这个答案中的简单模式,直到我需要更复杂的东西。自定义“异步命令”可以更好地处理一些用例:繁忙的微调器/禁用时执行、取消命令、数据绑定结果/错误。 My latest async command stuff is here.
    【解决方案2】:

    MvvmCross 现在有 MvxAsyncCommand(参见 GitHub commit)。

    所以不要这样做

    public ICommand MyCommand
    {
      get
      {
        return new MvxCommand(async () => await DoMyCommand());
      }
    }
    

    你可以这样做

    public ICommand MyCommand
    {
      get
      {
        return new MvxAsyncCommand(DoMyCommand);
      }
    }
    

    【讨论】:

    • 按照async 方法命名约定的建议使用Asyncreturn new MvxAsyncCommand(DoMyCommandAsync);
    【解决方案3】:

    看起来不错,只是我最终会在那个 await 周围添加一个 try catch。

        public async void DoMyCommand()
        {
            IsBusy = true;
            try{
                await Task.Factory.StartNew(() =>
                                            {
                    _myService.LongRunningProcess();
                });
            }catch{
                //Log Exception
            }finally{
                IsBusy = false;
            }
        }
    

    此外,我的博客上有一个使用异步 MvxCommand 的示例。非常类似于您的示例http://deapsquatter.blogspot.com/2013/03/updating-my-mobile-apps-for-async.html

    【讨论】:

      【解决方案4】:

      您还可以使用 MethodBinding plugin 来避免样板代码(命令),并将您的 UI 直接绑定到异步方法。

      此外,如果您使用Fody PropertyChanged,您的代码将如下所示:

      [ImplementPropertyChanged]
      public class MyModel: MvxViewModel
      {
          private readonly IMyService _myService;
      
          public bool IsBusy { get; set; }
      
          public MyModel(IMyService myService)
          {
              _myService = myService;
          }
      
          public async Task DoSomething()
          {
              IsBusy = true;
              await Task.Factory.StartNew(() =>
              {
                      _myService.LongRunningProcess();
              });
              IsBusy = false;
          }
      }
      

      您可以像这样进行绑定:“Click DoSomething”。

      另一方面,与其使用await Task.Factory.StartNew(),为什么不让_myService.LongRunningProcess 异步呢? 看起来会好很多:

      public async Task DoSomething()
      {
          IsBusy = true;
          await _myService.LongRunningProcess();
          IsBusy = false;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多