与ReactiveUI 6的线程关联

时间:2015-10-06 07:30:03

标签: c# wpf reactive-programming reactiveui

方案(WPF桌面应用程序,.NET 4.6):

我有一个列表框,显示一些"任务"。 目标是启动一个异步过程,迭代所有任务,执行每个任务。

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

问题

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

  

这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。

我的问题是:如何使用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;
        });
    });

}

1 个答案:

答案 0 :(得分:2)

All UI updates (ReactiveList add/remove, or IPlugin property change) need to happen in UI thread. In your case, assuming item.Execute() is the lengthy operation you want to happen in the background, you should use async/await instead of Task.Run, e.g: your code should look like:

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());
}

Have a look at this reference if you need more inspiration.