使用ReactiveUI的异步API填充集合的最佳方法是什么?

时间:2014-01-17 16:31:30

标签: c# asynchronous system.reactive reactiveui

让我们说我有这个ReactiveUI视图模型结构,Model是一些任意模型类型。

class ViewModel : ReactiveObject
{
    private readonly ReactiveList<Model> _models;
    public IReactiveList<Model> Models { get { return _models; } }

    public IReactiveCommand LoadModels { get; private set; }
    public bool LoadingModels { get; private set; } // Notifies;
}

这些模型来自这个界面建模的基于任务的异步API:

interface ITaskApi
{
    Task<IEnumerable<Model>> GetModelsAsync();
}

在看了Octokit.net的反应库是如何编写之后,我编写了以下类来使API适应被动反应的世界:

class ObservableApi
{
    private readonly ITaskApi _taskApi;

    public IObservable<Model> GetModels() {
        return _taskApi.GetModelsAsync().ToObservable().SelectMany(c => c);
    }
}

现在,我已经编写了以下方法来在LoadModels构造函数的ViewModel()命令中实现模型的加载:

// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand(this.WhenAny(x => x.LoadingModels, x => !x.Value));

// First method, with the Observable API;
LoadModels.Subscribe(_ =>
    {
        LoadingModels = true;
        _models.Clear();
        observableClient.GetModels().ObserveOnDispatcher()
            .Subscribe(
                m => _models.Add(m),
                onCompleted: () => { LoadingModels = false; });
    });

// Second method, with the task API;
LoadModels.Subscribe(async _ =>
    {
        LoadingModels = true;
        try {
            var loadedModels = await taskClient.GetModelsAsync();
            _models.Clear();
            _models.AddRange(loadedModels);
        } catch (Exception ex) {
            RxApp.DefaultExceptionHandler.OnNext(ex);
        } finally {
            LoadingModels = false;
        }
    });

虽然我认为两种方式都可以胜任,但我有以下疑虑:

  • 在第一个示例中,我应该处理内部订阅还是在内部observable完成或错误时完成?
  • 在第二个示例中,我知道将会吞下GetModelsAsync方法中引发的异常。

从异步枚举中填充ReactiveList<T>的“最佳”,最惯用的方式是什么(IObservable<T>Task<IEnumerable<T>>(或IObservable<IEnumerable<T>>会更好?) )?

1 个答案:

答案 0 :(得分:2)

  

在看了Octokit.net的反应库是如何编写之后,我编写了以下类来使API适应被动反应的世界:

虽然您有时想要这样做(即展平集合),但将它保留为IEnumerable<T>通常会更方便,除非您计划在每个项目上调用异步方法在列表中。由于我们只想将所有内容填入List中,因此我们不想这样做。只需将其保留为Task<T>

即可
  

在第一个示例中,我应该处理内部订阅还是在内部observable完成或错误时完成?

如果您在另一个订阅中有订阅,您可能会想要SelectMany运算符。但是,有更好的方法可以执行您要执行的操作,您应该查看this docs article以获取更多信息。

所以,这就是我编写代码的方式:

// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand();

LoadModels.RegisterAsyncTask(_ => taskClient.GetModelsAsync())
    .Subscribe(items => 
    {
        // This Using makes it so the UI only looks at the collection
        // once we're totally done updating it, since we're basically
        // changing it completely.
        using (_models.SuppressChangeNotifications())
        {
            _models.Clear();
            _models.AddRange(items);
        }
    });

LoadModels.ThrownExceptions
    .Subscribe(ex => Console.WriteLine("GetModelsAsync blew up: " + ex.ToString());

// NB: _loadingModels is an ObservableAsPropertyHelper<bool>
LoadModels.IsExecuting
    .ToProperty(this, x => x.LoadingModels, out _loadingModels);