setNeedsDisplay broken in Tiger
-
I have a thread that wants to tell a view to update and then the
thread must wait until the view is updated before continuing. This
thread worked fine until Tiger. The code fragment is
[myView setNeedsDisplay:YES]
NSLog(@"needsDisplay is now %d",(int)[myView needsDisplay]); //
it shows "1"
[myView displayIfNeeded]; // force update (but was not
needed before Tiger)
while(YES)
{ [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
0.2]];
if([myView needsDisplay]==NO) break;
}
But the loop that waits until the view is updated never exits. I
checked and drawRect is being called and successfully finished, but
needsDisplay is never set back to NO. This was all fine until
compiling in Tiger. What has changed in Tiger about updating views in
a thread? Or is there another way to have a thread wait until a view
has completed an update?
----------------
John Nairn (1-801-581-3413, FAX: 1-801-581-4816)
Web page: http://www.mse.utah.edu/~nairn -
On May 24, 2005, at 9:31 AM, John Nairn wrote:
> I have a thread that wants to tell a view to update and then the
> thread must wait until the view is updated before continuing. This
> thread worked fine until Tiger. The code fragment is
>
> [myView setNeedsDisplay:YES]
> NSLog(@"needsDisplay is now %d",(int)[myView
> needsDisplay]); // it shows "1"
> [myView displayIfNeeded]; // force update (but was not
> needed before Tiger)
> while(YES)
> { [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
> 0.2]];
> if([myView needsDisplay]==NO) break;
> }
>
> But the loop that waits until the view is updated never exits. I
> checked and drawRect is being called and successfully finished, but
> needsDisplay is never set back to NO. This was all fine until
> compiling in Tiger. What has changed in Tiger about updating views
> in a thread? Or is there another way to have a thread wait until a
> view has completed an update?
This shouldn't have ever worked. These messages are not thread-safe,
and must be sent on the main thread. In general, Cocoa isn't thread-
safe except in places where the documentation explicitly says it is,
and those NSView messages are not exceptions.
As currently documented, ONLY the following NSView messages are
thread-safe:
-beginDocument
-lockFocusIfCanDraw
-lockFocusIfCanDrawInContext:
-bob -
On 24-May-05, at 12:31 PM, John Nairn wrote:
> Or is there another way to have a thread wait until a view has
> completed an update?
The best way is to use NSLock to synchronize the threads.
But I think you can get away with using your own flag - e.g. with a
BOOL variable declared as 'volatile' and depend on the atomicity of
reading & writing this variable.
--
Cameron Hayne
<hayne...> -
On May 24, 2005, at 9:31 AM, John Nairn wrote:
> I have a thread that wants to tell a view to update and then the
> thread must wait until the view is updated before continuing. This
> thread worked fine until Tiger. The code fragment is
>
> [myView setNeedsDisplay:YES]
> NSLog(@"needsDisplay is now %d",(int)[myView
> needsDisplay]); // it shows "1"
> [myView displayIfNeeded]; // force update (but was not
> needed before Tiger)
> while(YES)
> { [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:
> 0.2]];
> if([myView needsDisplay]==NO) break;
> }
>
> But the loop that waits until the view is updated never exits. I
> checked and drawRect is being called and successfully finished, but
> needsDisplay is never set back to NO. This was all fine until
> compiling in Tiger. What has changed in Tiger about updating views
> in a thread? Or is there another way to have a thread wait until a
> view has completed an update?
>
> ----------------
> John Nairn (1-801-581-3413, FAX: 1-801-581-4816)
> Web page: http://www.mse.utah.edu/~nairn
As Bob Ippolito pointed out, messaging views from a secondary thread
is in general not thread-safe and therefore not a recommended
practice, on any version of Mac OS X.
If you must force a view to be redrawn synchronously, and block on
completion of the redraw, you can have your secondary thread send the
view a "display" message on the main thread:
[someControllerObject performSelectorOnMainThread:@selector
(displayTheView:) withObject:myView waitUntilDone:YES];
where someControllerObject implements:
- (void)displayTheView:(id)theView {
[theView display];
}
Regarding the -needsDisplay behavior you are seeing: Is "myView"
fully visible, or is part of it clipped by an ancestor view (e.g. an
NSScrollView/NSClipView)? A view may be marked as needing display
anywhere within its bounds, including areas that are not within its
current "visibleRect" (areas, for example, that are currently
scrolled out of view). It is therefore possible for -needsDisplay to
report YES after a view's visible area has been redrawn, if occluded
portions of the view remain invalidated. On Tiger, this state is
respected and preserved more consistently than it was on Panther.
--
Troy Stephens
Cocoa Frameworks
Apple Computer, Inc. -
On May 24, 2005, at 11:32 AM, Troy Stephens wrote:
> As Bob Ippolito pointed out, messaging views from a secondary
> thread is in general not thread-safe and therefore not a
> recommended practice, on any version of Mac OS X.
To clarify a bit: There _are_ some aspects of view functionality that
are OK to use in a multithreaded manner. For example, locking focus
to draw in a view on a secondary thread is supported.
The -setNeedsDisplay: and -setNeedsDisplayInRect: operations,
however, are not thread-safe, and should therefore always be
performed on the application's main thread.
--
Troy Stephens
Cocoa Frameworks
Apple Computer, Inc. -
On May 24, 2005, at 4:01 PM, Troy Stephens wrote:
> On May 24, 2005, at 11:32 AM, Troy Stephens wrote:As Bob Ippolito
> pointed out, messaging views from a secondary thread is in general not
> thread-safe and therefore not a recommended practice, on any version
> of Mac OS X.
> To clarify a bit: There _are_ some aspects of view functionality that
> are OK to use in a multithreaded manner. For example, locking focus
> to draw in a view on a secondary thread is supported.
>
> The -setNeedsDisplay: and -setNeedsDisplayInRect: operations, however,
> are not thread-safe, and should therefore always be performed on the
> application's main thread.
Interesting contrast with Java AWT, where the repaint() method on
Component is one of the very few AWT/Swing operations which *is*
explicitly thread safe and can be called from any thread.
Of course, they achieve that by queuing repaint events on the AWT event
queue and thus actually firing the paint from the AWT Thread "some time
later" (thus it really is serial, but the application developer can
ignore that). This does allow them to do repaint coalescing to cut down
on unnecessary paints.
How interesting that -setNeedsDisplay: is exactly the opposite; it
would seem a perfect candidate for some kind of queue... I'd love to
know why it's that way :)
AndyT (lordpixel - the cat who walks through walls)
A little bigger on the inside
(see you later space cowboy ...) -
On May 24, 2005, at 9:43 PM, Andrew Thompson wrote:
>
> On May 24, 2005, at 4:01 PM, Troy Stephens wrote:
>
>
>> On May 24, 2005, at 11:32 AM, Troy Stephens wrote:As Bob Ippolito
>> pointed out, messaging views from a secondary thread is in general
>> not thread-safe and therefore not a recommended practice, on any
>> version of Mac OS X.
>> To clarify a bit: There _are_ some aspects of view functionality
>> that are OK to use in a multithreaded manner. For example,
>> locking focus to draw in a view on a secondary thread is supported.
>>
>> The -setNeedsDisplay: and -setNeedsDisplayInRect: operations,
>> however, are not thread-safe, and should therefore always be
>> performed on the application's main thread.
>>
>
> Interesting contrast with Java AWT, where the repaint() method on
> Component is one of the very few AWT/Swing operations which *is*
> explicitly thread safe and can be called from any thread.
>
> Of course, they achieve that by queuing repaint events on the AWT
> event queue and thus actually firing the paint from the AWT Thread
> "some time later" (thus it really is serial, but the application
> developer can ignore that). This does allow them to do repaint
> coalescing to cut down on unnecessary paints.
>
> How interesting that -setNeedsDisplay: is exactly the opposite; it
> would seem a perfect candidate for some kind of queue... I'd love
> to know why it's that way :)
It's not that way, you're mistaken. Calling setNeedsDisplay: doesn't
cause anything to happen immediately either, but it's not safe to
call from random threads whenever you want to. Read the docs, when a
view is set as needing display, it will be displayed during the next
pass of the event loop.
Cocoa's threading model is quite simple: unless something is
explicitly thread safe, it's almost certainly not. If it happens to
be, pretend it's not, because that's an implementation detail that's
subject to change. Threads should be rather isolated from one
another in general; so if you find yourself wishing that more of
Cocoa was thread safe, then you should probably learn some more about
preemptive multithreading first.
-bob -
On May 25, 2005, at 3:07 AM, Bob Ippolito wrote:
> It's not that way, you're mistaken. Calling setNeedsDisplay: doesn't
> cause anything to happen immediately either, but it's not safe to call
> from random threads whenever you want to. Read the docs, when a view
> is set as needing display, it will be displayed during the next pass
> of the event loop.
That makes perfect sense, it just leaves me wondering why what's
presumably such a simple method wouldn't be thread safe. One wonders if
it's an efficiency thing - assuming it's just a boolean flag, then
you'll have problems unless NSLock (or possibly volatile) is used to
make the changes visible across threads. Or maybe it's something else
entirely. That's what I'm curious about, but I doubt anyone at Apple is
going to say :)
> Cocoa's threading model is quite simple: unless something is
> explicitly thread safe, it's almost certainly not. If it happens to
> be, pretend it's not, because that's an implementation detail that's
> subject to change. Threads should be rather isolated from one another
> in general; so if you find yourself wishing that more of Cocoa was
> thread safe, then you should probably learn some more about preemptive
> multithreading first.
The exact same rule applies to AWT/Swing in Java for precisely the same
reasons. Very few methods are documented thread safe.
Many things in fact do work from multiple threads, but, especially if
you're targeting multiple platforms, you're better off behaving as if
they're not thread safe and doing everything from the AWT thread, which
plays the role of the main thread in AppKit.
So you're preaching to the choir here. I understand the principle, and
whilst it would be nice if more things in both toolkits (Cocoa,
AWT/Swing) were thread safe, it's certainly no silver bullet! I'm just
curious about the reasons behind things.
AndyT (lordpixel - the cat who walks through walls)
A little bigger on the inside
(see you later space cowboy ...) -
Andrew,
On 25.5.2005, at 16:00, Andrew Thompson wrote:
> ... it would be nice if more things in both toolkits (Cocoa, AWT/
> Swing) were thread safe
I am not that sure myself. Thread-safeness tends to be costly, and,
in a vast majority of cases, actually not needed at all: although
there definitely are tasks to be served best by threads, very very
often it is *much* better just to use event loop properly.
---
Ondra ÄŒada
OCSoftware: <ocs...> http://www.ocs.cz
private <ondra...> http://www.ocs.cz/oc -
On May 25, 2005, at 7:00 AM, Andrew Thompson wrote:
>> It's not that way, you're mistaken. Calling setNeedsDisplay:
>> doesn't cause anything to happen immediately either, but it's not
>> safe to call from random threads whenever you want to. Read the
>> docs, when a view is set as needing display, it will be displayed
>> during the next pass of the event loop.
>>
>
> That makes perfect sense, it just leaves me wondering why what's
> presumably such a simple method wouldn't be thread safe. One
> wonders if it's an efficiency thing - assuming it's just a boolean
> flag, then you'll have problems unless NSLock (or possibly
> volatile) is used to make the changes visible across threads. Or
> maybe it's something else entirely.
Well, to truly call it "thread safe," you'd need to handle other
threads interacting with that HIView at the same time as your code
interacts with it--i.e. what if you call setNeedsDisplay while
another thread is currently rendering, resizing, moving, or otherwise
altering the view? You have to account for anything that might put
the NSView object in an unstable state.



