BackgroundWorker RunWorkerCompleted 未在 UI 线程上执行
BackgroundWorker RunWorkerCompleted not executed on UI thread
我已经围绕 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
的构造函数完成的,将导致所描述的问题。之后通过命令绑定使用 UI 按钮进行的任何其他更新绝对没问题。
所以问题是,在初始加载时,MainWindow
ctor、ViewModel
ctor 和 Worker.Execute()
在 UI 线程上执行,但是 workCompleted
不是,尽管在调用 RunWorkerAsync
.
时 SynchronizationContext
应该设置为 UI 线程
每当通过命令绑定触发下一次加载时,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();
}
}
处理RunWokerCompleted
事件的线程由调用RunWorkerAsync
方法时的SynchronizationContext.Current
属性决定。
因此,如果您的 Execute
方法实际上是在 WPF 应用程序的 UI 线程上调用的,其中 SynchronizationContext.Current
属性 returns a DispatcherSynchronizationContext
, workCompleted
也将在此线程上调用。您可以通过在 MainWindow
:
的构造函数中调用 Execute()
轻松地自己确认这一点
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
new Worker().Execute(() => { Thread.Sleep(5000); return 0; }, (ex, jobArray) =>
{
MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());
});
}
}
顺便说一句,Task Parallel Library (TPL) 是自 .NET Framework 4 以来编写多线程和并行代码的首选方式。任务异步编程模型 (TAP) 以及 async
和 await
.NET 4.5 中引入的关键字使其更易于使用。
通过将作业的初始加载从 MainWindow
构造函数移动到 MainWindow.Loaded
事件来解决问题。
出于某种原因,BackgroundWorker 在 RunWorkerCompleted
的 UI 线程上没有 SynchronizationContext
,除非 UI 已完全初始化。
public partial class MainWindow : Window
{
private MainWindowViewmodel _viewmodel;
public MainWindow(Connection connection)
{
InitializeComponent();
_viewmodel = (MainWindowViewmodel)DataContext;
Loaded += MainWindowLoaded;
}
private void MainWindowLoaded(object sender, RoutedEventArgs e)
{
_viewmodel.UpdateJobs();
}
}
我已经围绕 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
的构造函数完成的,将导致所描述的问题。之后通过命令绑定使用 UI 按钮进行的任何其他更新绝对没问题。
所以问题是,在初始加载时,MainWindow
ctor、ViewModel
ctor 和 Worker.Execute()
在 UI 线程上执行,但是 workCompleted
不是,尽管在调用 RunWorkerAsync
.
SynchronizationContext
应该设置为 UI 线程
每当通过命令绑定触发下一次加载时,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();
}
}
处理RunWokerCompleted
事件的线程由调用RunWorkerAsync
方法时的SynchronizationContext.Current
属性决定。
因此,如果您的 Execute
方法实际上是在 WPF 应用程序的 UI 线程上调用的,其中 SynchronizationContext.Current
属性 returns a DispatcherSynchronizationContext
, workCompleted
也将在此线程上调用。您可以通过在 MainWindow
:
Execute()
轻松地自己确认这一点
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
new Worker().Execute(() => { Thread.Sleep(5000); return 0; }, (ex, jobArray) =>
{
MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());
});
}
}
顺便说一句,Task Parallel Library (TPL) 是自 .NET Framework 4 以来编写多线程和并行代码的首选方式。任务异步编程模型 (TAP) 以及 async
和 await
.NET 4.5 中引入的关键字使其更易于使用。
通过将作业的初始加载从 MainWindow
构造函数移动到 MainWindow.Loaded
事件来解决问题。
出于某种原因,BackgroundWorker 在 RunWorkerCompleted
的 UI 线程上没有 SynchronizationContext
,除非 UI 已完全初始化。
public partial class MainWindow : Window
{
private MainWindowViewmodel _viewmodel;
public MainWindow(Connection connection)
{
InitializeComponent();
_viewmodel = (MainWindowViewmodel)DataContext;
Loaded += MainWindowLoaded;
}
private void MainWindowLoaded(object sender, RoutedEventArgs e)
{
_viewmodel.UpdateJobs();
}
}