【问题标题】:Thread affinity with ReactiveUI 6与 ReactiveUI 6 的线程亲和性
【发布时间】:2015-10-06 07:30:03
【问题描述】:

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

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

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

问题

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

}

【问题讨论】:

    标签: c# wpf reactive-programming reactiveui


    【解决方案1】:

    所有 UI 更新(ReactiveList 添加/删除,或 IPlugin 属性更改)都需要在 UI 线程中发生。在您的情况下,假设 item.Execute() 是您希望在后台发生的 longy 操作,您应该使用 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

    【讨论】:

    • 太棒了。错误消失了,它可以正常工作而不会像预期的那样阻塞 UI,非常感谢。但有一件事,状态的变化 - item.status = /*new_status*/ 并没有向 UI 发出信号。集合正在改变,但用户界面没有改变。我让它工作的方式是在每次更改(两次)之后添加Tasks.Reset(),这似乎不正确。
    猜你喜欢
    • 2017-12-06
    • 1970-01-01
    • 2011-01-15
    • 2012-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多