与 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。
场景(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。