与 ReactiveUI 6 的线程关联

Thread affinity with ReactiveUI 6

场景(WPF 桌面应用程序,.NET 4.6):

我有一个显示一些 "tasks" 的列表框。 目标是启动一个异步进程,该进程将遍历所有任务,执行每一个任务。

这是一个很长的 运行 过程,因此所需的行为是在不锁定 UI 的情况下禁用大部分命令,以便用户仍然可以取消它。 它应该标记每个任务(待命、运行、完成),以便 UI 可以动态更新以向最终用户提供反馈(使用基于 "Status" 枚举的样式)。

问题

执行命令 (ExecuteTasks) 时,我收到消息:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

我的问题是:如何使用 ReactiveUI 解决这个问题?我相信答案就在调度程序的某个地方,但到目前为止我还想不出来。

代码如下所示:

public ReactiveCommand<object> AddTask { get; }
public ReactiveCommand<object> ExecuteTasks { get; }
public IPlugin SelectedTask
{
    get { return selectedTask; }
    set { this.RaiseAndSetIfChanged(ref selectedTask, value); }
}
public ReactiveList<IPlugin> Tasks
{
    get { return tasks; }
}
public ReactiveList<PluginFactoryGroup> TaskFactories
{
    get
    {
        return taskFactories;
    }
}

public AppViewModel(IExecutionContext context, IEnumerable<PluginFactoryGroup> taskFactories)
{

    this.context = context;

    // initialize lists
    tasks = new ReactiveList<IPlugin>() { ChangeTrackingEnabled = true };
    this.taskFactories = new ReactiveList<PluginFactoryGroup>(taskFactories);

    // create observables to determine whether or not commands can be executed
    var canEdit = /*...*/
    var canExecute = /*...*/

    // initialize commands
    AddTask = ReactiveCommand.Create(canEdit);
    AddTask.Subscribe(_ => {
        if (SelectedFactory != null)
        {
            var t = SelectedFactory.Create(this.context);
            Tasks.Add(t);
            SelectedTask = t;
        }
    });

    ExecuteTasks = ReactiveCommand.CreateAsyncTask(canExecute, _ =>
    {
        return Task.Run(() =>
        {
            object result = null;
            foreach (var item in Tasks)
            {
                item.Clear();
                item.Validate();
            }

            if (Tasks.Any(e => e.Status == TaskStatus.Error))
            {
                Tasks.Reset();
                return result;
            }

            foreach (var item in Tasks)
            {
                item.Status = XrmTools.Plugins.TaskStatus.Running;
                item.Execute();
                item.Status = XrmTools.Plugins.TaskStatus.Completed;
            }
            return result;
        });
    });

}

所有 UI 更新(ReactiveList add/remove 或 IPlugin 属性 更改)需要在 UI 线程中发生。在你的情况下,假设 item.Execute() 是你想要在后台发生的 lengthy 操作,你应该使用 async/await 而不是 Task.Run,例如:您的代码应如下所示:

ExecuteTasks = ReactiveCommand.CreateAsyncTask(canExecute, async _ =>
{
    object result = null;
    foreach (var item in Tasks)
    {
        item.Clear();
        item.Validate();
    }

    if (Tasks.Any(e => e.Status == TaskStatus.Error))
    {
        Tasks.Reset();
        return result;
    }

    foreach (var item in Tasks)
    {
        item.Status = XrmTools.Plugins.TaskStatus.Running;
        await ExecuteAsync(item);
        item.Status = XrmTools.Plugins.TaskStatus.Completed;
    }
    return result;
});

Task ExecuteAsync(IPlugin item)
{
    return Task.Run(() => item.Execute());
}

如果您需要更多灵感,请查看此reference