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 (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/luesang%40apple.com

This email sent to [EMAIL PROTECTED]


--------------------------
RONZILLA



_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [EMAIL PROTECTED]

Reply via email to