-
Notifications
You must be signed in to change notification settings - Fork 779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BindableCollection dispatches operations on the collection to the UI thread - why not just the change events? #247
Comments
This code predates my taking over the project so I can't speak to the logic of why almost all the operations were wrapped in I'd love to see a pull request with your implementation so we can discuss it further. On a side note the implementation of |
As a work around: Reactive Extension can be used here to throttle or buffer updates |
does anyone have any idea how to write tests for this change? |
You could potentially test this by creating a fake platform provider that recorded calls to dispatch on the UI thread. Your test could then do a number of operations and then assert the amount of ui thread dispatches was correct. |
see ticket and tickets discussion for rationale. Tests missing yet (have to figure out how to proper test with thread dispatching).
I guess the fix up to here doesn't solve the problem but just flips it around: The commit I sent before "fixes" this by doing anything except the Update-Notifications off the UI-Thread. A better solution would be to create a specific thread to do the collection changes on. The BindableCollection would take a distinct Manipulator-Thread. This Thread get's it's own Dispatcher. This way I hope to solve both concerns, the one raised by @cdanne and the one solved by the original solution to always use the UI-Thread. |
after discussion with @TeaDrivenDev and others today I came to the following concept to solve this issue: The goal is to avoid (blocking) work on the UI thread, while keeping the collection manipulations thread safe. My approach would be to use the same approach, but a different, dedicated thread to process the work. It get's complicated when the other platforms are introduced, as there are three different Dispatcher implementations to target. What i have locally yet consists of an extension to the IPlatformProvider by a method to get a Dispatcher The BindableCollection then can get this dispatcher - and with it a dedicated thread that sleeps most of the time as long as nothing happens on the Collection. My current problem is to get some kind of unified Dispatcher object with a unified API across the platforms that can be used for this. Missing parts yet:
|
This feels like a lot of work for what should be a simple collection. The only real reason it exists is because a lot of the implementations of The added complications of other platforms just get terrible. Right now the bug issue is we're dispatching in a chatty way, rather than a chunky way, It may be best to keep the solution to just that. Making it a completely thread safe collection while doing this can be a separate piece of work (with an associated discussion on whether it's necessary / better way to do it). |
@nigel-sampson thanks for the comment, but I oppose: By the initial commit we don't "keep the solution to just that", but we introduce possible bugs that were not possible before. But Sometimes having a good night isn't a bad thing, and talking to others isn't either. Basically we came to the conclusion that the Dispatching stuff isn't even necessary to be thread save. All we want is not losing the concurrency-safety the BindableCollection had before (as that runs the risk to break existing code using the BindableCollection) while reducing the UI-Thread work. By a simple lock we achieve the same goal across threads: A single lock (per collection instance) is used wrapping all manipulation commands of the base implementation. As this locking is done on a single level only (by wrapping the base implementation), and there is only one lock object involved, it should be impossible to get a deadlock (deadlock always needs two interleaving locks). Unit tests still working, but I want to do some more testing tomorrow on real-life code. Nevertheless I appreciate any comments and remarks (except for the typo in the issue number in the commit message - realized that myself). |
…tions see Issue thread for more details, but the lock on the manipulationLock field prevents concurrent modification of the collection now, where dispatching to the UI-Thread and by that accidentally linearizing these manipulations did the same.
Looking over this some more with the view to release I want to tackle this a different way. The first is something akin to what got brought up in #407 as a way to opt out of both I think as evidenced here different people have different goals and opinions on what they want, especially when it comes to high performance updates. What I propose is adding a new virtual method that will handle dispatching on both
This should allow customization in most scenarios to what people want to do, such as add a boolean property to turn it off at times, or there own custom thread dispatching. Then in Thoughts? |
@nigel-sampson but how can this be used for all built-in PropertyChangedBase inheriting classes? |
The commit 6a8d29d adds the same behavior to |
Hi @nigel-sampson, Are you suggesting to subclass PropertyChangedBase, Screen, Conductor etc and override OnUIThread for each one manually? If so it sounds very cumbersome to say the least. IMHO this should be controlled centrally and affect all existing relevant composition classes (simply switch this marshaling behavior off), see you comment. |
I'm not totally convinced it should be global. I've seen some scenarios where it being on an instance basis is better. If you truly want to disable it, you could use a custom platform provider to stop all marshalling to the UI thread. |
I have tens of view models deriving from PropertyChangedBase, Screen, Conductor etc, and I simply do not wish that all property changes will be marshaled to the UI thread. IMHO it doesn't make sense to manually override OnUIThread per VM, and also not to create NotUIMarshaledPropertyChangedBase, NotUIMarshaledScreen etc. WPF is handling property changes internally, please allow a way to disable this globally, since the default will be UI marshaling enabled, this won't be a breaking change. "I would consider adding it to IPlatformProvider since it's static and then be configured solution wide." sounds like a perfect solution. Thank you for everything. |
If you're looking for sweeping changes I'd recommend creating a subclass of |
Hi there,
we have a scenario where a BindableCollection is not only modified by user interaction (drag & drop of list items), but also may get many updates in short time from a background operation. In the latter case, performance is an issue and we set IsNotifying=false before the algorithm starts and re-enable change events afterwards.
That improved performance because it removed UI updates, but didn't make it great... We profiled the app and realized that a major performance killer is the fact that all operations on the collection (Add, Remove etc.) are dispatched to the UI thread via Execute.OnUIThread(...) in the BindableCollection implementation.
Question: Why so? Isn't it enough to have the overwritten OnCollectionChanged() and OnPropertyChanged() raise the change event on the UI thread?
So there would be no waiting for the UI thread to modify the collections in the first place. Dispatching to the UI thread only happens for the change events and that only in case IsNotifying is true.
We did such an implementation and it performs better and seems to work OK in our scenario. Are there any pitfalls with that approach which we oversee or would such an implementation be equally "correct" and faster in such scenarios?
The text was updated successfully, but these errors were encountered: