wentnet.com Home
Misc Home
Using NSProxy in multithreaded programs in Cocoa on Mac OS X
Below is a sample program to show how NSProxy can be used to communicate between threads in a multithreaded Cocoa application. Also described is a problem I ran into when using this method in XNJB.
main.m
#import <Foundation/Foundation.h>
#import "MainThread.h"

int main (int argc, const char *argv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  MainThread *m = [[MainThread alloc] init];
  [m doStuff];

  NSLog(@"releasing the MainThread");
  [m release];

  [pool release];
  return 0;
}


The main function just allocates the MainThread object and tells it do doStuff.
MainThread.h
#import <Cocoa/Cocoa.h>

@interface MainThread : NSObject {
  int someVoidCount;
}
- (void)doStuff;
- (void)someVoid;


The MainThread class definition.
MainThread.m
#import "MainThread.h"
#import "WorkerThread.h"

@implementation MainThread

- (void)doStuff
{
  NSLog(@"doStuff in thread %d", [NSThread currentThread]);

  someVoidCount = 0;

  NSPort *port1 = [NSPort port];
  NSPort *port2 = [NSPort port];
  NSConnection *kitConnection = [[NSConnection alloc] initWithReceivePort:port1 sendPort:port2];
  [kitConnection setRootObject:self];

  // Ports switched here
  NSArray *portArray = [NSArray arrayWithObjects:port2, port1, nil];

  WorkerThread *t = [[WorkerThread alloc] init];

  [t detach:portArray];

  NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
  [runLoop run];

  NSLog(@"releasing the WorkerThread");

  [t release];
}

- (void)someVoid
{
  someVoidCount++;
  NSLog(@"someVoid in thread %d, count %d", [NSThread currentThread], someVoidCount);
}

@end


This is the class for the main thread. Firstly, the NSConnection object is set up. The line [kitConnection setRootObject:self]; sets the object that will receive messages from the other thread. Calling [t detach:portArray]; detaches a new thread, the worker thread. Then the run loop is run (forever) so the worker thread can run.
WorkerThread.h
#import <Cocoa/Cocoa.h>

@interface WorkerThread : NSObject {
}
- (void)detach:(NSArray *)ports;
- (void)initConnection:(NSArray *)ports;


The WorkerThread class definition.
WorkerThread.m
#import "WorkerThread.h"

@implementation WorkerThread

- (void)detach:(NSArray *)ports
{
  NSLog(@"detach: in thread %d", [NSThread currentThread]);
  [NSThread detachNewThreadSelector:@selector(initConnection:) toTarget:self withObject:ports];
}

- (void)initConnection:(NSArray *)ports
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSLog(@"initConnection: in thread %d", [NSThread currentThread]);

  NSConnection *serverConnection = [NSConnection connectionWithReceivePort:[ports objectAtIndex:0] sendPort:[ports objectAtIndex:1]];

  id mainThreadProxy = [serverConnection rootProxy];

  [mainThreadProxy someVoid];

  NSLog(@"initConnection: completed");

  [pool release];
}

@end


The detatch: method calls initConnection: in a new thread. Then, the connection with the server is set up and the proxy object obtained. This is a proxy for the rootObject set in the main thread. Any messages sent to this object will be forwarded to the MainThread instance and executed in the main thread. So, as an example, someVoid is called. This will run in the main thread.
A problem
Using the above method, I discovered a problem with XNJB when updating the progress bars. During a long file transfer the program crashed every time at the same point (in my case, just before it had transfered 256 MB). The program crashed when forwarding a message to the proxy object.

To isolate this problem, I modified the above code to call someVoid 65536 (or 0xffff) times by replacing the single call with a loop:

int i = 0;
for (i = 0; i < 0xffff; i++)
{
  [mainThreadProxy someVoid];
}


This crashed every time when making the 65531st call. I do not know why, but I assume I have either missed something above or there is a bug in the Cocoa libraries. Anyway, I found a workaround which is to create and release an autorelease pool either side of the call, like this:

NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];
[mainThreadProxy someVoid];
[pool1 release];


If anyone can offer any insight into why this now works or why it didn't work before I'd be very interested to hear from you.
Copyright © 1999-2010 Richard Low. All trademarks on this page are owned by their respective owners. Hosting by Memset.