Reputation: 280
I have written a small wrapper around 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();
}
}
I use it in the viewmodel (which is created in XAML) like so:
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));
});
}
}
When jobs are added in workCompleted
to ObservableCollection _jobs
, I get the following exception:
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
So obviously the RunWorkerCompleted
callback doesn't run on the UI thread. But as far as I know it should run on the one thread from which RunWorkerAsync
was called? And this should be the UI thread, since it is called from inside the viewmodel (which was created from XAML) from a command binding (also UI thread), right?
What am I missing here?
Edit2:
I could narrow down the problem to the initial loading of jobs. This is done from the ctor of MainWindow
and will result in the described problem. Any additional updates afterwards using the UI button via command binding work absolutely fine.
So the problem is, on initial loading, MainWindow
ctor, ViewModel
ctor and Worker.Execute()
are executed on the UI thread, but workCompleted
is not, although the SynchronizationContext
should be set to the UI thread at the moment of invoking RunWorkerAsync
.
Whenever the next loading is triggered via command binding, the workCompleted
is also set to the SynchronizationContext
of the UI thread.
Any idea why this isn't working in the first place?
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();
}
}
Upvotes: 0
Views: 577
Reputation: 280
Solved the problem by moving the initial loading of the jobs from the MainWindow
constructor to the MainWindow.Loaded
event.
For some reason the BackgroundWorker doesn't have the SynchronizationContext
on the UI thread for RunWorkerCompleted
unless the UI is fully initialized.
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();
}
}
Upvotes: 1
Reputation: 169200
The thread on which the RunWokerCompleted
event is handled is determined by SynchronizationContext.Current
property at the time the RunWorkerAsync
method is called.
So if your Execute
method is actually called on the UI thread in a WPF application, where the SynchronizationContext.Current
property returns a DispatcherSynchronizationContext
, workCompleted
will be invoked on this thread as well. You easily can confirm this yourself by calling Execute()
in the constructor of the MainWindow
:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
new Worker().Execute(() => { Thread.Sleep(5000); return 0; }, (ex, jobArray) =>
{
MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());
});
}
}
By the way, the Task Parallel Library (TPL) is the preferred way to write multithreaded and parallel code since .NET Framework 4. The task asynchronous programming model (TAP) and the async
and await
keywords that were introdued in .NET 4.5 make it even easier to use.
Upvotes: 3