我该如何在mvvmcross视图模型中使用异步操作?

28

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

Xamarin的beta频道目前支持异步关键字。

以下是我当前实现异步的示例。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;
    }

}
4个回答

34

你应该避免使用 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.Run; 在 async 代码中,Task.Run 是首选,原因由 Stephen Toub 在 这篇文章 中描述。


这个很好用: return new MvxCommand(() => DoMyCommand()); - Chris Koiak
3
async 的 lambda 表达式提供了稍微不同的错误处理方式。使用 () => DoMyCommand(),任何来自 DoMyCommand 的异常都会被静默地忽略。而使用 async () => await DoMyCommand(),任何来自 DoMyCommand 的异常都将被视为未处理异常。 - Stephen Cleary
@ChrisKoiak,你能否在博客文章或文章中给出如何实现和使用IAlertService的示例,或者在这里进行概念性说明? - Shawn Mclean
@Shawn 在这里试试吧:https://dev59.com/vXLYa4cB1Zd3GeqPZ596 - Chris Koiak
1
@Vackup:我倾向于遵循这个答案中的简单模式,直到我需要更复杂的东西。自定义的“异步命令”可以更好地处理一些用例:繁忙旋转器/执行时禁用、取消命令、数据绑定结果/错误。我的最新异步命令内容在此处 - Stephen Cleary
显示剩余3条评论

6

MvvmCross现在有了(请参阅GitHub提交

因此,不再需要这样做:

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

您可以这样做。
public ICommand MyCommand
{
  get
  {
    return new MvxAsyncCommand(DoMyCommand);
  }
}

1
按照异步方法命名约定,使用Asyncreturn new MvxAsyncCommand(DoMyCommandAsync); - SwiftArchitect

4

看起来还不错,只是我建议在 await 前加上 try catch finally。

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

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


1

您也可以使用MethodBinding 插件来避免样板代码(命令),并直接将您的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;
    }
}

你可以这样绑定: "点击 DoSomething"。
另一方面,与其使用 await Task.Factory.StartNew(),为什么不将 _myService.LongRunningProcess 改为异步的呢? 这样看起来会更好:
public async Task DoSomething()
{
    IsBusy = true;
    await _myService.LongRunningProcess();
    IsBusy = false;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接