Re: KVO notifications and threads
Re: KVO notifications and threads
- Subject: Re: KVO notifications and threads
- From: Ron Lue-Sang <email@hidden>
- Date: Mon, 14 Jul 2008 20:59:13 -0700
On Jul 14, 2008, at 8:58 AM, Dave Dribin wrote:
On Jul 11, 2008, at 2:36 PM, Bill Bumgarner wrote:
If you have specific enhancement requests, please file a bug via http://bugreporter.apple.com
. If your request is "make MVC and KVO play nicely with threads",
you will need to provide details on exactly what you mean --
exactly how that is to be achieved.
I've got a bug filed along these lines: rdar://5953181 (mistakenly
filed under OS X Server :/). The main times I've come across this
problem is when I want to show the progress of a background
operation by binding some property to a progress indicator.
Totally reasonable.
Thus, instead of doing this:
self.progress = newProgress;
I resort to setting "progress" on the main thread using
performSelectonOnMainThread: + no wait, similar to Clark's
suggestion. I figure not waiting allows the background thread to
continue to maintain decent concurrency. [1]
Yep. That's a good way to do it. And yes, "no wait" means the call
returns immediately so you don't end up serializing work between
threads needlessly.
I'm not sure of the best way to make threads + Cocoa bindings play
nice.
For the case you've described above, the solution you've described is
a great solution in terms of correctness, even if it's not a one-click
solution.
My current thinking is to have AppKit bounce all KVO notifications
over to the main thread. It could use NSThread's isMainThread to
conditionally bounce the notification to the main thread. It could
even coalesce notifications, as Ben suggested.
That'd be great as a solution for just the one problem you're
describing (well, actually, I think it sounds great at first and then
it would lead to much pain down the line). But then you've got
something that's easy, but isn't quite correct. This is what a lot of
the replies to your initial question have implied. Bill's already said
you don't want a "thread safe" AppKit. At least It sounds like you've
realized that as well.
The argument I'm trying to make is that you, as the app implementor,
have everything you need to do what you mean and do it correctly.
Imagine this (this is kinda long winded, so buckle up):
- You have a view bound to property foo of DataObject.
- You also have WorkerObject bound to property foo of DataObject
- You have the same view bound to a property "bar" of WorkerObject
which depends on DataObject.foo
- WorkerObject is doing it's work with DataObject in a background
thread, and the view is drawing in the main thread
In the scenario you're asking for, DataObject.foo changes and the view
"bounces" the KVO notification over to the main thread.
//
// for anyone reading who doesn't realize, this would really mean doing
// [self performSelectorOnMainThread:@selector(observeValueForKeyPath:
…)… ], and forwarding the method arguments.
//
Now WorkerObject gets the notification (again, observeValueForKeyPath
gets invoked), and it changes it's bar property. This happens in the
same thread that the change to DataObject happened. So it could happen
before the main thread gets a chance to run and handle that
performSelectorOnMainThread call earlier (assuming no wait).
But the behaviour or contents of the bound view depends on BOTH
DataObject.foo and WorkerObject.bar. Does the order in which these
notifications arrive make a difference? Maybe. AppKit would never
know, and the ordering would be indeterminate. So this wouldn't work.
What would be a better way to do this?
Coalesce the notifications on the background thread yourself. Post
both notifications, or just one über notification, in just the order
you need. Then, this (sorta contrived) example would work with your
KVO-bouncing AppKit, right? Well, it would also work great with our
existing regular AppKit too. Because what you meant to happen isn't
obvious, so you didn't leave it up to AppKit to infer.
Does that make sense?
Another possibility is to have have another @property option which
forces that property to only be updated on the main thread. Or
maybe just have the willChange/didChange happen on the main thread.
I'm not sure it would make sense to treat the main thread so specially
in Foundation/ObjC API. I think this encourages the wrong kind of
laziness.
Or finally, maybe NSController should take care of bouncing model
KVO notifications to the main thread.
This wouldn't help in all cases, like when you create your own non-
NSController controller. You'd be back to doing what everyone has
already suggested - take care of this in your own controller code.
My gut tells me this should be done in AppKit.
I'm not trying to be snide (not this time at least) but that would
imply that your gut is outnumbered - at least on this list.
The model and the view should be completely isolated from each
other. Forcing the model to so stuff on the main thread because the
view (i.e AppKit) is not thread safe seems like breach of MVC.
I haven't looked in a long time, what do the MVC design patterns
guides say about threads?
Anyway, I wouldn't leave this up to the model objects either. I'd put
this logic into your controller layer. But as I said earlier, I don't
think making NSController do this is the right answer either. You're
the only one who knows which notifications to coalesce together and
what order things should be done in, etc. It's not obvious (to me at
least) how you'd imbue any of the NSController's with that knowledge.
If AppKit is not thread safe, then AppKit should be responsible for
not crashing in the face of threads. Making AppKit thread is not at
all the right solution, but bouncing KVO notifications seems to be a
decent one. While it may be a performance bottleneck, it's
certainly better than nondeterministic crashing, which is what we
get with the current behavior. I can always Shark it to improve
performance, which I'd rather do than hunt down some non-100%-
reproducible crasher.
-Dave
[1]: I've written a little helper to make performing on the main
thread a little easier, especially when dealing with primitive types:
<http://www.dribin.org/dave/blog/archives/2008/05/22/invoke_on_main_thread/
>
Thus, you can write:
[[self dd_invokeOnMainThread] setProgress:newProgress];
without having to box up newProgress as an NSNumber. It's done for
you by forwardInvocation:.
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden
--------------------------
RONZILLA
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden