True separation of code and presentation while using the Dispatcher
In my WPF try to separate my classes logic from any interface related data and only supply ObservableCollection properties for binding.
Problem is, when I access those binded OCs from other threads, I am required to do so through the dispatcher. The result is that I am required to add many Dispatcher.Invoke() call开发者_Go百科s, hidden inside my classes, whenever one of the methods attempts to update the OCs.
How can I do that in a more clean and separated way, so the dispatcher calls be abstracted away from my methods?
I don't have a silver bullet. But if you are certain and ready to take the responsibility of implicit UI delegation, you can always inherit from ObservableCollection
, override methods and dispatch all requests to UI.
But the following code makes me scary:
// somewhere in thread pool:
for(int i = 0; i < 1000; i++)
{
_dispatcherAwareCollection.Add(i);
}
It seems innocent, but under the hood it blocks calling thread 1000 times. Alternatives might be your specific BulkXXX()
methods, that will delay notification until all elements are processed. This solution is not perfect either, since you wanted an abstraction that could let you seamlessly swap collections, but BulkXXX()
methods are very specific to new collection.
Option 1
I think you should look into a better separation of your code using the MVVM pattern, if you aren't familiar with it, I highly suggest to see the following video as it explains exactly what you're looking for.
Specifically, however, in your case you should have the model class with regular collection (e.g List) on which you do all the work in the threads. Your ViewModel should contain the ObservableCollections and connect loosely with the collections that exist in the model, e.g, you can choose to subscribe via an event from your ViewModel to a certain update logic in your model. You will STILL need to use Dispatcher to update the OC, but you will only need to do it once.
Option 2
You can instead just use the solution described here. Basically, he created a new derived class from OC that allows you to dispatch changes from the code automatically without you ever needing to update the dispatcher yourself.
The common approach is to have a Dispatcher property on your view model (probably in a base class for all view models) that can be injected outside. It is OK to have it in a view model because view model SHOULD be aware of UI concepts, but it should not be aware of particular view (layout, controls, etc.) and certainly it should not have a reference to the view.
What you can do is you can make it easier to dispatch your code to the Dispatcher thread by creating a helper or a service that will abstract the dispatcher away. For example, you can create a helper like this:
public class AsyncHelper
{
public static void EnsureUIThread(Action action)
{
if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.BeginInvoke(action, DispatcherPriority.Background);
}
else
{
action();
}
}
}
And whenever you need to update you observable collection, you wrap you code in that helper method:
AsyncHelper.EnsureUIThread(() =>
{
// Update you observable collections here
});
OR, you can go further and use AOP (e.g. PostSharp) to specify declaratively (using attributes) that a method should be executed in the UI thread.
And finally, please note that you have to dispatch only collection updates to the UI thread. Usual properties can be safely updated from a background thread. The updates will be dispatched to the UI thread automatically by the binding mechanism. Probably in future versions of WPF updates to a collection from a background thread also will be supported.
Well you could write yourself an AsyncObservableCollection
, if you know how to write it threadsafe. Then you can encapsulate the Dispatcher
calls in it.
The problem is you would not use the standard ObservableCollection
delivered within the .Net - Framework. It would increase the risk of errors in your application.
Another option would be to implement a WrapperClass, which contains and exposes an ObservableCollection
for binding and has methods to modify the collection.
public class WrapperClass<T>
{
public ObservableCollection<T> Collection {get; set;}
public void Add(T item)
{
//do your dispatcher magic here
}
...
}
To modify the collection you implement the methods in it. The problem here is, that there is no guarantee, that others will use these methods, too.
I am afraid that you will have to wait for the next version of wpf
From this post:
A few nuggets we can expect to see in the next version of WPF include:
- Hosting of Silverlight content with the new SilverlightHost element, without airspace issues (the inability to overlap WPF content over native Windows hWnd content)
- Overall better management of airspace with hosted native hWnd-based content like the WebBrowser, HwndHost and WindowsFormsHost
- Enabling binding and change notification for collections that are created on a background thread
- Better integration with UI virtualization
- Integration of the Ribbon control
- And more
Use SynchronizationContext instead of Dispatcher. SynchronizationContext is common feature for threads synchronization in .NET, meanwhile Dispatcher is intentionally developed for WPF.
You probably want to use something like MTObservableCollection. I've used this in a project and it worked fantastically. Basically, it does all the dispatching work for you when the collection changed event is raised, by analysing the thread that the handler was assigned from, and dispatching accordingly.
The article is well worth a read, even if you don't plan to take this option.
I have an extension for this:
public static class DispatcherInvoker
{
public static void AddOnUI<T>(this ICollection<T> collection, T item)
{
Action<T> addMethod = collection.Add;
Application.Current.Dispatcher.BeginInvoke(addMethod, item);
}
}
EDIT: I stole it from an stackoverflow post but forgot from which one
I think you have to much coupling if you need to think about threading in your model layer.
What you should do is to not connect your model directly to the GUI. As others have said, use a layer in between (MVVM).
This means that you let your MVVM layer respond to the change notifications from your observable collection. It is the MVVM layer that decides if and how these notifications should be passed on to the GUI. Look here for a way to lower the update frequency of the GUI to keep it usable.
In short: Keep using an ObeservableCollection in your model layer if you like but don't use it directly in the GUI binding. Let another layer receive the notifications and control the GUI update.
精彩评论