WPF MVVM Multithreading Issue
I've seen other issues similar to mine, but i haven't seen any that i can apply to make my code work.
So i'm new to MVVM, and i'm trying to get some stuff that's executing in a background thread to update on my UI. What i'm noticing is that the first time bring up the UI, and the background thread executes the first time, if the collection is IEnumerable<> the UI isn't fully updated against backing data. If the collection is ObservableCollection<>, it throws an error.
From what i've read, changes to collections need to be executed on the dispatcher thread, but OnPropertyChanged() calls do not. So someone, please tell me how this could be happening:
I'm altering my _Printers observable collection:
foreach (PrinterViewModel pv in _Printers)
{
DispatcherExec(() =>
{
开发者_如何学Go var abilities = from x in _ServerData.Types
select new PrinterAbility(
new PrintableType() { ID = x.ID, Name = x.Name, NumInProcUnit = x.NumInProcUnit, PrintersMappedTo = x.PrintersMappedTo, SysName = x.SysName },
x.PrintersMappedTo.Contains(pv.Printer.ID)
);
pv.Printer.SetAbilities(abilities);
});
My DispatcherExec looks like so:
private void DispatcherExec(Action action)
{
//Dispatcher.Invoke((Action)delegate
//{
// action.BeginInvoke(null, null);
//}, null);
Dispatcher.CurrentDispatcher.Invoke((Action)delegate
{
action.Invoke();
}, null);
}
And here's the SetAbilities code that fails:
public void SetAbilities(IEnumerable<PrinterAbility> abilities)
{
if (log.IsInfoEnabled)
log.Info("SetAbilities(IEnumerable<PrinterAbility> abilities): called on printer "+Name);
List<PrinterAbility> l = new List<PrinterAbility>();
abilities.ForEach(i =>
{
i.PrinterAbilityChanged += new PrinterAbilityChangedEventHandler(OnPrinterAbilityChanged);
l.Add(i);
}
);
lock (_Abilities)
{
foreach (PrinterAbility pa in l)
_Abilities.Add(pa);
}
if (log.IsDebugEnabled)
log.Debug("SetAbilities(IEnumerable<PrinterAbility> abilities): leaving");
}
On the _Abilities.Add(pa) observable collection add it says "This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread." I'm thinking, "are you joking?"
Further, I would think that a change to an object in the observable collection would automatically make it call OnCollectionChanged(), is that right?
Thanks in advance everyone.
Using Dispatcher.CurrentDispatcher is not something you should do from a BG thread. You need to use the Dispatcher for a DependencyObject-derived object that has been created on the UI thread.
Also, you're iterating over *ViewModel objects (PrinterViewModel) from within a BG thread. This really goes against MVVM. Your model should be doing asynchronous stuff, and your ViewModel(s) should be handling those asynchronous operations in a way that the view can consume (by marshalling to the proper thread via the Dispatcher).
Also, you're closing over a loop variable (pv). Bad, bad. This (depending on the order of execution) could mean that by the time the dispatcher comes around, you'll get multiple pv.Printer.SetAbilities(...) calls on the same PrinterViewModel instance. Create a local variable inside the loop and use that within your anonymous method to avoid this problem.
You should use the Dispatcher associated with any of your WPF controls, not the Dispatcher.CurrentDispatcher for the background thread.
Also
Dispatcher.CurrentDispatcher.Invoke((Action)delegate
{
action.Invoke();
}, null);
is redundant, it should be
wpfDispatcher.Invoke(action, null);
And finally for the first block, you should usually avoid passing loop variables to the lambdas, use temporary assignment trick to get around these sneaky closure problems. Almost certain that it's not the problem in this case though.
精彩评论