方案(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;
});
});
}
答案 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.