I'd often like to block the main thread while another thread or process performs a little task, but subject to a short timeout. A few weeks ago, I was able to achieve this by "running" the main thread's run loop while an NSTask completed. But I did this after hours of experimenting and still don't understand how it works. Today, when I tried to generalize the code, waiting for a little worker thread instead of an NSTask, my run loop just gets stuck.

I know the trouble is that, although I have read the NSRunLoop documentation and related material in the Threading Programming Guide many times, I just don't understand the magic behind this:

   while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                   beforeDate:[NSDate distantFuture]])
   {
     // do something
   }

I know that it only runs when an "input source" fires, and so I believe I need to add an input source, but when I try to do that I end up creating ports and distributed objects, which is obviously wrong.

Below is a little demo project. If you look at the end, in main(), you'll see that, in Test #1, I start a job with a duration of 1.0 seconds and a timeout of 2.0 seconds. Expected result: The job should complete and program should continue. Actual result: Run loop never "fires", so the program gets stuck. (In Test #2, I would start a similar job which ^should^ time out.)

Can anyone show how to fix this?

Sincerely,

Jerry Krinock


#import <Cocoa/Cocoa.h>

NSString* const SSYThreadBlockerIsDoneNotification = @"SSYThreadBlockerIsDoneNotification" ;

@interface SSYThreadBlocker : NSObject
{
    // To do:  @synchronize access to these?
    BOOL isDone ;
    BOOL didTimeout ;
}

@property BOOL isDone ;
@property BOOL didTimeout ;

@end

@implementation SSYThreadBlocker

@synthesize isDone ;
@synthesize didTimeout ;


- (void)timeout:(NSTimer*)timeoutTimer
{
    NSLog(@"380 %s", __PRETTY_FUNCTION__) ;
    if (![self isDone]) {
        [self setDidTimeout:YES] ;
    }
    [self setIsDone:YES] ;
}

- (void)workerDone:(NSNotification*)note
{
    NSLog(@"533 %s", __PRETTY_FUNCTION__) ;
    [self setIsDone:YES] ;
}


/*!
 @brief    Starts a job on another thread and blocks the current thread
 until the job notifies that it is complete, subject to a timeout

 @details  When the job is complete, the worker must post an
 SSYThreadBlockerIsDoneNotification notification from the default
 notification center, with the notification object set to itself.
 @param    worker  The target which will perform the job.
 @param    selector  The selector which defines the job.  If no
 garbage collection, this selector must create and destroy an
 autorelease pool.
 @param    object  A parameter which will be passed to selector
 @param    workerThread  The thread on which the job will be performed.
 If you pass nil, a temporary thread will be created.
 @param    timeout  The time before the job is aborted.
 @result   YES if the job completed, NO if it timed out.
*/
+ (BOOL)blockUntilWorker:(id)worker
                selector:(SEL)selector
                  object:(id)object
                  thread:(NSThread*)workerThread
                 timeout:(NSTimeInterval)timeout
{
    SSYThreadBlocker* instance = [[SSYThreadBlocker alloc] init] ;
NSTimer* timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:instance selector:@selector(timeout:)
                                                           userInfo:nil
repeats:NO] ;

    [[NSNotificationCenter defaultCenter] addObserver:instance
selector:@selector(workerDone:) name:SSYThreadBlockerIsDoneNotification
                                               object:worker] ;

    // Begin Work
    if (workerThread)
    {
        [worker performSelector:selector
                       onThread:workerThread
                     withObject:object
                  waitUntilDone:NO] ;
    }
    else
    {
        // Default if no workerThread given is to create one
        workerThread = [[[NSThread alloc] initWithTarget:worker
                                          selector:selector
object:object] autorelease] ;
        // Name the thread, to help in debugging.
        [workerThread setName:@"Worker created by SSYThreadBlocker"] ;
        [workerThread start] ;
    }

    // Re-run the current run loop.  (Magic)
    NSLog(@"1415 Will 'run' the Run loop") ;
    while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                    beforeDate:[NSDate distantFuture]])
    {
        NSLog(@"2813: Run loop did fire") ;
        if ([instance isDone]) {
            break ;
        }
    }
    NSLog(@"1731 Run loop is done") ;

    [timeoutTimer invalidate] ;

    [[NSNotificationCenter defaultCenter] removeObserver:instance
name:SSYThreadBlockerIsDoneNotification object:timeoutTimer] ;
    BOOL didTimeout_ = [instance didTimeout] ;
    [instance release] ;

    return (!didTimeout_) ;
}

@end


@interface WorkerDemo : NSObject {}

- (void)doWorkForTimeInterval:(NSNumber*)interval ;

@end


@implementation WorkerDemo

- (void)doWorkForTimeInterval:(NSNumber*)interval
{
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init] ;
    NSLog(@"2308: Beginning work") ;

    NSTimeInterval intervalValue = [interval doubleValue] ;
    usleep(1e6 * intervalValue) ;

[[NSNotificationCenter defaultCenter] postNotificationName:SSYThreadBlockerIsDoneNotification
                                                        object:self] ;

    NSLog(@"2557: Ending work") ;
    [pool release] ;
}

@end



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

    WorkerDemo* workerDemo = [[WorkerDemo alloc] init] ;
    NSTimeInterval duration ;
    NSTimeInterval timeout ;
    BOOL ok ;

    NSLog(@"Starting Test #1") ;
    duration = 1.0 ;
    timeout = 2.0 ;
    ok = [SSYThreadBlocker blockUntilWorker:workerDemo
selector:@selector(doWorkForTimeInterval:) object:[NSNumber numberWithDouble:duration] thread:nil // SSYThreadBlocker will create a thread
                                    timeout:timeout] ;
    NSLog(@"Job duration=%0.0f  timeout=%0.0f  succeeded=%d",
          duration,
          timeout,
          ok) ;


    NSLog(@"Starting Test #2") ;
    duration = 3.0 ;
    timeout = 2.0 ;
    ok = [SSYThreadBlocker blockUntilWorker:workerDemo
selector:@selector(doWorkForTimeInterval:) object:[NSNumber numberWithDouble:duration] thread:nil // SSYThreadBlocker will create a thread
                                    timeout:timeout] ;
    NSLog(@"Job duration=%0.0f  timeout=%0.0f  succeeded=%d",
          duration,
          timeout,
          ok) ;

    [pool release] ;
}

_______________________________________________

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 arch...@mail-archive.com

Reply via email to