CocoaDev

Edit AllPages

One of the limitations with Apple’s implementation of General/NSThread is that threads are not stoppable or joinable. After a few months I finally came up with the following code and examples. Thought I’d share it to the world.

FBThread.h

#ifndef FBTHREAD_H #define FBTHREAD_H 1

#import <Cocoa/Cocoa.h> #include

static const General/NSString *General/FBThreadAbort = @”General/FBThreadAbort”;

/*! @class General/FBThread @abstract Fundamental thread class based on General/AppKit’s General/NSThread, threading protocol. @discussion An General/FBThread object controls a thread of execution. Use General/FBThread when you want to have an Objective-C method run in its own thread of execution. Threads are especially useful when you need to perform a lengthy task, but don�t want it to block the execution of the rest of the application. In particular, you want to avoid freezing the user interface, preventing the user from performing additional actions. Threads can also be used to divide a large job into several smaller jobs. Then, if the computer has multiple processors, the jobs can be distributed among them for greater efficiency.

Unlike, General/NSThread, General/FBThread has been designed similar to Java’s implementation of its thread class. Subclass General/FBThread and override the run method with the code you wish to execute. This code will run after calling either start or startDetached. The difference between the two methods is that start will linearly execute the run method without creating a new thread, while startDetached will load the class into a new thread and begin execution. If the code you wish to run lengthy or encapsulates loops, be sure to poll isRunning to determine if the thread should stop execution. For example:

@code

/*! @method start @abstract Starts the code in the run method. @discussion This method calls upon the run method to execute the user defined code that should be implemented in it’s subclass. If General/NSThread is not subclassed, the run method will do nothing. If you wish to execute the code in the run method in a seperate thread you must execute startDetached. Unlike General/NSThread, this thread implementation sets up the autorelease pool environment for you. @seealso startDetached */

/*! @method startDetached @abstract Starts a new thread. @discussion If this thread is the first thread detached in the application, this method posts the General/NSWillBecomeMultiThreadedNotification with object nil to the default notification center. Unlike General/NSThread, this thread implementation sets up the autorelease pool environment for you. */

/*! @method stop @abstract Stops a running thread. @discussion Gracefully attempts to stop a running thread. If the thread is currently running, this method will send a message to the run loop to terminate. However, this will not force the thread to terminate, it’s the run loop’s responsibility to poll the isRunning method to determin if the thread should terminate or not. If you want forceably terminate the thread use stopBeforeDate:. @seealso stopBeforeDate: */

/*! @method stopBeforeDate: @abstract Stops a running thread within a specified time frame. @discussion Gracefully attempts to stop a running thread before the specified time. If the thread is currently running, this method will send a message to the run loop to terminate. Like stop, it’s the user’s responsibility to poll isRunning to gracefully terminate thre thread. If the thread does not terminate by the specified time frame, it will first raise an General/NSThreadAbort exception. It is the user’s responsibility to catch the exception so that it may gracefully shut down. If the exception is not caught the operating system will deal with the exception as necessary. However, if for whatever reason the exception is ignored, this method will wait approximately 10 seconds before using the POSIX functions to adrubtly terminate the thread. @param date Date and time that the thread should termiante by. If you don’t wish to wait, specify a time in the past. @result YES if the thread gracefully shutdown before the specified time, NO if it had to be done forceably. */

/*! @method join @abstract Joins a running thread. @discussion An in instance where you wish for a thread to complete it’s operations you may join it. Joining a thread will pause the main thread or calling thread’s execution and wait for the instance class to terminate it’s operations. If the thread uses an indefinite loop, it’s possible that join will never return. */

/*! @method joinBeforeDate: @abstract Joins a running thread until the specified time frame. @discussion An instance where you wish for a thread to complete it’s operations within a specified time frame you may join it. Joining a thread will pause the main thread or calling thread’s execution and wait for the instance class to terminate it’s operations before the specified date. If the thread does not terminate before the specified time frame, then the calling thread will continue it’s execution. @result YES if the thread terminated before the specified time, NO if otherwise. */

/*! @method run @abstract Main thread loop @discussion This is the thread’s main execution loop. This method should be overridden by the subclass. Be sure to poll isRunning to determine if the loop should terminate or continue execution. Also, implement @try - @catch - @finally block to catch General/NSThreadAbort exception if the thread is forceably terminated. */

/*! @method isRunning @abstract Determines if the thread is running. @discussion This method should be called upon from the run loop frequently. It allows the code to know if the thread should continue running or if it should keep running. @result YES if the thread should continue running, NO if otherwise. */

/*! @method isDetached @abstract Determines if the code is running as a thread or linear code. @discussion This method helps other portions of the code to identify if the class instance was loaded as a thread or just regular code. @result YES if the class was loaded as a thread, NO if otherwise. */

@end

#endif // FBTHREAD_H

FBThread.M

#import “General/FBThread.h”

// Lock conditions enum { General/FBThreadStateNotStarted, // The thread has not started General/FBThreadStateRunning, // The thread is currently running General/FBThreadStateTerminating, // The thread is terminating General/FBThreadStateTerminated // The thread has terminated };

// Private implementation @interface General/FBThread (General/PrivateAPI)

/*! @method threadBridge: @abstract Bridges General/NSThread to the class @discussion Used to bridge General/[NSThread detachNewThreadSelector:toTarget:withObject:] with the run method. It establishes the autorelease pool prior to calling the subclass’ run method. */

@end

@implementation General/FBThread

/*! @method signalToExceptionBridge(int signal) @abstract Bridges signals to an General/NSException. @discussion This function bridges a signal to an General/NSException. */ void signalToExceptionBridge(int signal) { General/NSException *e = General/[NSException exceptionWithName:@”Test” reason:General/[NSString stringWithFormat:@”Aborting thread: %@”, General/[NSDate date]] userInfo:nil]; @throw e; }

main.m

#import <Cocoa/Cocoa.h> #import “General/FBThread.h”

@interface Worker : General/FBThread @end

@implementation Worker

int main(int argc, char *argv[]) { General/NSAutoreleasePool *pool = General/[[NSAutoreleasePool alloc] init]; // This is just for testing purposes. Worker *thread = General/Worker alloc] init]; @try { int x;

	[thread start];	// Runs linearly, will last about 25 second
	[thread startDetached];	// Runs as a seperate thread
	for (x = 0; x < 6; x++) {
		usleep(500000);
		[[NSLog(@"I'm still waiting...");
	}
	General/NSLog(@"The thread should be done...%@", ([thread isRunning] ? @"I guess I was wrong" : @"Yup I was right!"));
	
	[thread startDetached];	// Runs as a seperate thread
	for (x = 0; x < 2; x++) {
		usleep(500000);
		General/NSLog(@"I'm still waiting...");
	}
	General/NSLog(@"The thread should still be running...%@", (![thread isRunning] ? @"I guess I was wrong" : @"Yup I was right!"));
	General/NSLog(@"Gracefully shut the bad boy down...");
	[thread stop];
	General/NSLog(@"The thread should be done...%@", ([thread isRunning] ? @"I guess I was wrong" : @"Yup I was right!"));
	
	[thread startDetached];	// Runs as a seperate thread
	for (x = 0; x < 2; x++) {
		usleep(500000);
		General/NSLog(@"I'm still waiting...");
	}
	General/NSLog(@"The thread should still be running...%@", (![thread isRunning] ? @"I guess I was wrong" : @"Yup I was right!"));
	General/NSLog(@"Shut the bad boy down NOW!...");
	[thread stopBeforeDate:General/[NSDate distantPast]];
	General/NSLog(@"The thread should be done...%@", ([thread isRunning] ? @"I guess I was wrong" : @"Yup I was right!"));
} @finally {
	General/NSLog(@"Done with the main entry point.");
	[Worker release];
	[pool release];
} }

I’m sorry for the simplistic example…but one of my implementation is much more complicated then this.

Cheers.

~ Enrique