C# 与ReactiveUI 6的线程关联
场景(WPF桌面应用程序.NET 4.6): 我有一个显示一些“任务”的列表框。 目标是启动一个异步进程,该进程将遍历所有任务,执行其中的每一个任务 这是一个长时间运行的过程,因此期望的行为是禁用大多数命令而不锁定UI,以便用户仍然可以取消它。 它应该标记每个任务(备用、运行、完成),以便可以动态更新UI,向最终用户提供反馈(使用基于“状态”枚举的样式) 问题 执行命令(C# 与ReactiveUI 6的线程关联,c#,wpf,reactive-programming,reactiveui,C#,Wpf,Reactive Programming,Reactiveui,场景(WPF桌面应用程序.NET 4.6): 我有一个显示一些“任务”的列表框。 目标是启动一个异步进程,该进程将遍历所有任务,执行其中的每一个任务 这是一个长时间运行的过程,因此期望的行为是禁用大多数命令而不锁定UI,以便用户仍然可以取消它。 它应该标记每个任务(备用、运行、完成),以便可以动态更新UI,向最终用户提供反馈(使用基于“状态”枚举的样式) 问题 执行命令(ExecuteTasks)时,我会收到以下消息: 此类型的CollectionView不支持从不同于Dispatcher线程的
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;
});
});
}
public ReactiveCommand AddTask{get;}
public ReactiveCommand ExecuteTasks{get;}
公共IPlugin SelectedTask
{
获取{return selectedTask;}
设置{this.RaiseAndSetIfChanged(ref selectedTask,value);}
}
公共反应列表任务
{
获取{返回任务;}
}
公共反应列表任务工厂
{
得到
{
返回工厂;
}
}
公共AppViewModel(IExecutionContext上下文,IEnumerable TaskFactorys)
{
this.context=上下文;
//初始化列表
tasks=new ReactiveList(){changetrackingabled=true};
this.taskFactorys=新的反应列表(taskFactorys);
//创建可观察对象以确定是否可以执行命令
var canEdit=/**/
变量canExecute=/**/
//初始化命令
AddTask=ReactiveCommand.Create(canEdit);
AddTask.Subscribe(=>{
如果(SelectedFactory!=null)
{
var t=SelectedFactory.Create(this.context);
任务。添加(t);
选择任务=t;
}
});
ExecuteTasks=ReactiveCommand.CreateAsyncTask(canExecute,=>
{
返回任务。运行(()=>
{
对象结果=空;
foreach(任务中的变量项)
{
item.Clear();
item.Validate();
}
if(Tasks.Any(e=>e.Status==TaskStatus.Error))
{
Tasks.Reset();
返回结果;
}
foreach(任务中的变量项)
{
item.Status=XrmTools.Plugins.TaskStatus.Running;
item.Execute();
item.Status=XrmTools.Plugins.TaskStatus.Completed;
}
返回结果;
});
});
}
所有UI更新(ReactiveList添加/删除或IPlugin属性更改)都需要在UI线程中进行。在您的情况下,假设item.Execute()
是您希望在后台执行的冗长操作,您应该使用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());
}
如果你需要更多灵感,不妨看看这个。太棒了。错误消失了,它不会像预期的那样阻塞UI,非常感谢。但是有一件事,状态的改变-
item.status=/*new\u status*/
并没有向用户界面发出信号。集合正在更改,但用户界面未更改。我让它工作的方法是在每次更改(两次)后添加Tasks.Reset()
,这似乎不正确。