【发布时间】:2020-01-08 12:04:30
【问题描述】:
我已经为 BackgroundWorker 写了一个小包装器:
public class Worker
{
public void Execute<T>(Func<T> work, Action<Exception, T> workCompleted)
{
var worker = new BackgroundWorker();
worker.DoWork += (o, ea) => { ea.Result = work(); };
worker.RunWorkerCompleted += (o, ea) => { workCompleted(ea.Error, (T)ea.Result); };
worker.RunWorkerAsync();
}
}
我在视图模型(在 XAML 中创建)中使用它,如下所示:
public class MainWindowViewmodel : INotifyPropertyChanged
{
private ObservableCollection<Job> _jobs = new ObservableCollection<Job>();
public ICollectionView JobsView { get; } = CollectionViewSource.GetDefaultView(_jobs);
public RelayCommand RefreshJobsCommand => new RelayCommand(x => UpdateJobs()); /*edit1*/
private void UpdateJobs() /*executed via command binding*/
{
new Worker().Execute(() => _webServiceManager.JobService.GetJobsByDate(settings.Limit, DateTime.Now.AddDays(-365)), (ex, jobArray) =>
{
jobArray.toList().ForEach(j=>_jobs.Add(j));
});
}
}
当workCompleted 中的作业添加到 ObservableCollection _jobs 时,我得到以下异常:
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
所以显然RunWorkerCompleted 回调不会在 UI 线程上运行。但据我所知,它应该在调用 RunWorkerAsync 的一个线程上运行?这应该是 UI 线程,因为它是从命令绑定(也是 UI 线程)的视图模型(从 XAML 创建)内部调用的,对吧?
我在这里错过了什么?
编辑2:
我可以将问题缩小到作业的初始加载。这是由MainWindow 的ctor 完成的,将导致所描述的问题。之后通过命令绑定使用 UI 按钮进行的任何其他更新都可以正常工作。
所以问题是,在初始加载时,MainWindowctor、ViewModelctor 和 Worker.Execute() 在 UI 线程上执行,但 workCompleted 不是,尽管 SynchronizationContext 应该设置为 UI调用RunWorkerAsync时的线程。
每当通过命令绑定触发下一次加载时,workCompleted 也会设置为 UI 线程的 SynchronizationContext。
知道为什么这首先不起作用吗?
public partial class MainWindow : Window
{
private MainWindowViewmodel _viewmodel;
public MainWindow(Connection connection)
{
InitializeComponent();
Debug.WriteLine("mainwindow-ctor: " + Thread.CurrentThread.ManagedThreadId.ToString());
_viewmodel = (MainWindowViewmodel)DataContext;
_viewmodel.UpdateJobs();
}
}
【问题讨论】:
-
不要使用 BackgroundWorker。使用任务和异步/等待
-
GetJobsByDate()是做什么的?它如何确保你传递给它的 lambda 在 ui 线程上执行?我的意思是_webServiceManager听起来并不同步。 -
你真的在 UI 线程上调用
UpdateJobs()吗?请提供一个MCVE来证明您的问题。 -
external 组件
_webServiceManager是同步实现的,所以我必须将所有调用包装到一个 Task.Run() 中,恕我直言不太好。跨度> -
@mm8 我为 UI 按钮绑定的命令添加了代码。这将触发执行
UpdateJobs(),它仍然应该是 UI 线程...?
标签: c# wpf multithreading mvvm backgroundworker