WPF Multithreading: Using Dispatcher but the UI still hangs?
I am having a bit of a problem with my UI ha开发者_JAVA百科nging even though I am using a Dispatcher, and before I get any further I'm wondering if it's the way I'm handling the data retrieval.
Right now I have my main window creating a View and ViewModel. Then, inside of a new Thread (using a Dispatcher) it sets the View.DataContext = ViewModel. A very large ObservableCollection is lazily created when the binding kicks in which is causing the slowdown. However, it seems that some of the other UI items that should be showing up before that slowdow don't actually show up.
private void ButtonClick(Object sender, RoutedEventArgs e)
{
MyView view = new MyView();
MyViewModel vm = new MyViewModel();
TabItem tabItem = new TabItem();
tabItem.Header = "MyView";
tabItem.Content = view;
MyTabCollection.Items.Add(tabItem);
Window working = new Working();
working.Show();
ThreadStart thread = delegate()
{
DispatcherOperation operation = Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(delegate()
{
view.DataContext = vm;
((FrameworkElement)view.Parent).Focus();
working.Close();
}
)
);
};
Thread theThread = new Thread(thread);
theThread.Start();
}
This basically says it's supposed to create a view and a viewmodel, then add the view to the tab collection I have (which means it should show the new tab at the least). And, it should also show a "Working..." window. After that, a separate thread is supposed to link the ViewModel to the view, focus on that tab and close the working window. The problem is that the first portion doesn't show until everything is done; The tab is not displayed and the working window is not shown until after the new Thread actually finishes (which causes the Working window to show/close right away). I'm guessing it might have to do with the way I retrieve the data, but I'm not sure. Here is the way it does it:
- Create View
- Create ViewModel
- Create TabItem with Content set to the View and add the TabItem to the TabCollection.
- Create/Show the "Working..." window
- Dispatcher: Set the View.DataContext = ViewModel. This event sets off the DataBindings, which in turn grab the ObservableCollection. Since the OC is created Lazily it is now being created (this is the bottleneck). <-- Is this messing up my separate thread/dispatcher?
- Dispatcher: Set Focus to the tab
- Close the "Working..." window
All your extra thread is doing is marshalling another call back to the dispatcher thread. Presumably you actually want to do work on the extra thread, or there's no point in creating it.
Ideally your extra thread should be fetching all the data appropriately, leaving you only to actually connect it all up in the dispatcher thread. The important thing is to decide which work you need to do on the UI thread and which work you need to do on the background thread.
Obviously your analysis of the problem is correct. Your view model is lazily loading data when it is needed, and this is not happening until the Dispatcher callback, at which point you are back on the UI thread again and everything is locked up.
In my opinion, the solution is to do the threading in the data access layer:
For collections: You can define special collections that return only items that have already been loaded from the upstream data source, then trigger loading of additional items on a separate thread when someone subscribes to INotifyCollectionChanged. When the additional items arreive, fire INotifyCollectionChanged events. When INotifyCollectionChanged is unsubscribed, cancel any pending load.
For totals and the like: Same idea. As data comes in the total increases and events occur (automatically for DependencyProperty or using INotifyPropertyChanged).
In addition, the data layer should have a parallel property to each collection, sum, or other delay-loaded value indicating whether it is fully loaded or not, allowing the UI to gray out sections that aren't fully loaded. It is also convenient to have an overall "loading" flag somewhere that can be used to gray out UI sections when anything at all is loading (easier to write the UI this way).
Note that sometimes an operation must block until the actual data has been retrieved. I think the easiest thing in this case is to provide methods in the data layer to force data to be loaded synchronously.
Your DispatcherPriority
is set to Normal
- try setting it to Background
as this may improve the rendering
精彩评论