Re: NSThread question - DO to make it sing
Re: NSThread question - DO to make it sing
- Subject: Re: NSThread question - DO to make it sing
- From: Miguel Morales <email@hidden>
- Date: Tue, 10 Jul 2001 09:54:57 -0700
Dear Candide,
I think your problem is much deeper than a simple bug. What you want is
multiple running threads, and this involves thread to thread
communication. This can be done using NSLocks, but in your case I think
you need the power and flexibility of Distributed Objects to really make
this sing. So first a couple of bug ideas, then a suggestion of how to
rewrite it with DO.
Here are a couple of ideas to look for the bug (by the way the debugger
deals with threads nicely, just pull down the thread bar to switch
between them). In the [self display] call, are you writing directly
back to the screen? I know that the AppKit is not thread safe, and this
can confuse things badly. The way to do this is to do the data
processing in the new thread, then pass the finished product back to the
main thread and have the main thread display it. This keeps the display
system from having to deal with more than one thread.
I also have a couple of stylistic suggestions. First, you need to
release the autorelease pool in doImage.
-(void)doImage
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc ] init];
[ self display ];
[pool release]; //New line
}
Second, make sure you are not calling your view object from the main
thread while it is working. Your object is not thread safe either
unless you carefully set it up with NSLock all over the place to manage
access by multiple threads (this is hard, which is why the AppKit is not
thread safe). This could also kill your program
Third, the way you are creating the new thread should work but is a
little odd - almost double pumped in that it threads itself. I also
don't think it is doing what you want. You want the new thread to be
independent of the main thread, than update the main thread with some
processed data. This requires a more sophisticated setup, using the
Distributed Objects framework. Now this will look scary, but DO is
really, really cool once you get it working. So in 2 steps, a simple
rewrite of your code to be more standard, than one using DO. So a more
standard way of calling this would be to write in the main thread:
Main Thread
----------------
[NSThread detachNewThreadSelector:@selector(setData) toTarget: myView
withObject: data];
then rewrite the call in myView to look like this:
myView
-----------
-(void) setData:(NSData *) data
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc ] init];
//Needed if in new thread, doesn't hurt otherwise
myData = [[NSData alloc] initWith
Data:data];
[self doImage]; //Already in a new thread
[pool release]; //Clean up afterwards
}
However, this would spawn a thread, but the main thread would wait for
the secondary thread to finish before continuing, and not give you any
of the functionality you want of the main thread plugging along. This
is where DO comes in. I will assume that there is a main object that is
running in a run loop, and that when the button gets pushed the method
-(void)button:(NSData *)data is called, and that there is another method
in the object that draws to the screen (also in the main object) called
-(oneway void)display:(bycopy NSData *)data.
in the main object (main thread)
---------------
//In some initialization routine (-init?)
masterConnection = [NSConnection defaultConnection]; //Gets the default connection
for this thread
[masterConnection setRootObject:self];
[masterConnection registerName:@"mainThread"]; //registers itself
on the network so it can take input
.........
-(void)button:(NSData *)data
{
[NSThread detachNewThreadSelector:@selector(setData) toTarget:
myView withObject: data];
}
in the myView object (will be in a new thread most of the time)
---------------
-(oneway void) setData:(bycopy NSData *) data
{
id mainProxy; //A proxy object for the main object in the main
thread (how we call back when done)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc ] init];
mainProxy = [NSConnection
rootProxyForConnectionWithRegisteredName:@"mainThread" host:@""];
//picks up the network connection mainThread, and makes a
proxy object so we can call home
myData = data; //Already a copy from the bycopy keyword
[self doImage]; //do processing, but DO NOT write to the screen
[mainProxy display: myData];
//Calls back to the main thread, giving it the updated
information, and telling it to draw it to
//the screen (so only the main thread draws to the screen)
[pool release];
//Clean up afterwards, thread should automatically die
because we have no run loop for this thread
}
Note the oneway and bycopy keywords. oneway tells the DO system that
setData can be run independently of the main thread, otherwise the main
thread will wait for the sub thread to do it's processing (not what you
want). bycopy tells DO to send a copy of the data object, not a proxy.
This can greatly reduce the overhead of sending messages in some
situations.
Now the caveats: I just typed this in, and DO can be confusing, so this
is probably not bug free (some of the DO mavens out there can point out
my mistakes :-) The DO is hard to learn at first. Look at
Object-Orient Programming and the Objective-C Language, and some of the
DO examples. But once you get DO it is very cool. With a couple of
very minor changes the above code can be changed so that the secondary
thread persists and can process data whenever you want (just have the
thread vend a connection and send the thread into a run loop), or the
data can be processed by a process on another machine (some people
prefer to set up ports instead of registering a name when just doing
thread to thread communication, but I do distributed processing, and
like the elegance of the same interface whether the communication is
local or remote) So DO is definitely worth the time, and have fun.
Hope this helps,
-Miguel F. Morales
>
Candide wrote:
>
>
>
I still have an error (signal 10 (SIGBUS)). I'll explain exactly what I
>
do:
>
>
I have a -(void)doImage; method in a custom NSView object. This is the
>
method I'm calling in the new Thread, and it goes like this:
>
>
-(void)doImage
>
{
>
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc ] init];
>
[ self display ];
>
}
>
>
the method in which I call it goes like this:
>
>
-(void) setData:(NSData *) data
>
{
>
myData = [[NSData alloc] initWithData:data];
>
cachedImage = nil;
>
[NSThread detachNewThreadSelector:@selector(doImage) toTarget:self
>
withObject:nil];
>
}
>
>
Still obscure to me what causes the crash...
>
>
> Le mardi 10 juillet 2001, ` 11:59, Stefan Jung a icrit :
>
>
>
> Am Dienstag, 10. Juli 2001 um 10:47 schrieb Candide Kemmler:
>
>
>
>> Hi,
>
>>
>
>> I have a button (several of them actually) which triggers a long
>
>> rendering. My application not being multithreaded, the button stays
>
>> blue for a long while, until the rendering's finished. I tried to do:
>
>>
>
>> [ NSThread detachNewThreadSelector:@selector(display) toTarget:self
>
>> withObject:nil ];
>
>>
>
>> But then I get
>
>>
>
>> Jul 10 10:46:50 TT[979] *** _NSAutoreleaseNoPool(): Object 0x145d390
>
>> of class NSBezierPath autoreleased with no pool in place - just
>
>> leaking
>
>> Jul 10 10:46:50 TT[979] *** _NSAutoreleaseNoPool(): Object 0x145d3f0
>
>> of class NSBezierPath autoreleased with no pool in place - just
>
>> leaking
>
>> Jul 10 10:46:50 TT[979] *** _NSAutoreleaseNoPool(): Object 0x145d300
>
>> of class NSCalibratedRGBColor autoreleased with no pool in place -
>
>> just leaking
>
>>
>
>> What's wrong with my call ?
>
> Your call is Ok, but you have to set up your own NSAutoreleasePool. For
>
> your primary thread this is done by your application object, for
>
> Threads you have to do it on your own.
>
>
>
> NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
>
> The *pool destroys itself at the next opportunity, but releases all
>
> autoreleased objects before. so there is no [pool release].
>
>
>
> Stefan