macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Integrating Xgrid into Cocoa Applications, Part 2
Pages: 1, 2, 3, 4, 5

The initializer stores the controller URL, and creates some component objects, such as the subTasks dictionary and the subTasksRunning set. The dictionary is used to store details of the subtasks, and the set is used to keep track of which of the subtasks are running and thus which are complete. The taskHasLaunched boolean flag is used to keep track of whether the task has been launched before. A DistributedTask should only be launched once; if you need to repeat a calculation, simply create a new DistributedTask.



A number of straightforward accessors follow.


-(void)setDelegate:(id)del {
    delegate = del;
}

-(id)delegate {
    return delegate;
}

-(unsigned)numberOfSubTasks {
    return [subTasks count];
}

-(NSString *)controllerURLString {
    return controllerURLString;
}

Note that many attributes of the class do not include setters. You can't change the controller URL after initialization, for example. Making aspects of a class immutable, like this, simplifies writing and using the class.

The method to add subtasks to the task looks like this:


// The working directory, output directory, standard input and output are all
// optional. Use nil if they are not to be used.
#define SubNSNullForNil(var)    ( var == nil ? (id)[NSNull null] : (id)var )
-(void)addSubTaskWithIdentifier:(id)identifier
    launchPath:(NSString *)launchPath
    workingDirectoryPath:(NSString *)workingDirPath
    outputDirectoryPath:(NSString *)outputDirPath
    standardInputPath:(NSString *)standardInputPath
    standardOutputPath:(NSString *)standardOutputPath {
    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
        identifier,                             SubTaskIdKey,
        launchPath,                             LaunchPathKey,
        SubNSNullForNil(workingDirPath),        WorkingDirectoryKey,
        SubNSNullForNil(outputDirPath),         OutputDirectoryKey,
        SubNSNullForNil(standardInputPath),     StandardInputKey,
        SubNSNullForNil(standardOutputPath),    StandardOutputKey,
        nil];
    [subTasks setObject:dict forKey:identifier];
    
    // Also add to the subTasksRunning set, which will be used to keep track of 
    // which subTasks have finished when the task is run.
    [subTasksRunning addObject:identifier];
}

This method has a lot of arguments. Each argument corresponds directly to an xgrid command option. As the comment states, you can pass nil for some of the arguments, and these will then not be passed along to xgrid.

The method itself adds a dictionary describing the subtask to the dictionary of all subtasks. The identifier of the subtask is used as the key in the subtasks dictionary. The keys of the dictionary describing the subtask were introduced above, and correspond directly with the arguments of the method. Note that arguments that are nil are substituted with an instance of NSNull, using the macro SubNSNullForNil. This is necessary because you can't enter nil in a dictionary.

The last line of the method adds the subtask identifier to the subTasksRunning mutable set. As explained above, this set will be used to determine how many subtasks are running, and how many have completed.

Now we are getting to the core of the class.


-(void)launch {    
    // Make sure this is the first launch
    if ( taskHasLaunched ) 
        @throw [NSException exceptionWithName:@"MultipleLaunchException"
            reason:@"Attempt to launch DistributedTask multiple times." 
            userInfo:nil];
    else 
        taskHasLaunched = YES;
    
    // Launch subtasks
    NSEnumerator *subTaskEn = [subTasks objectEnumerator];
    NSMutableDictionary *subTaskDict;
    while ( subTaskDict = [subTaskEn nextObject] ) {
        // Create thread where xgrid task will be run
        [NSThread detachNewThreadSelector:
                @selector(runSubTaskAsynchronouslyWithDictionary:)
            toTarget:self 
            withObject:subTaskDict];
    }
    
    // Notify delegate of launch and initial progress
    if ( delegate && 
        [delegate respondsToSelector:
            @selector(distributedTaskDidLaunchSubTasks:)] ) 
        [delegate distributedTaskDidLaunchSubTasks:self];
}

The launch method runs the task. It first checks to see if the task has previously run; if it has, an exception is thrown. I use the new Objective-C exception handling facilities throughout Photo Industry, so if you haven't given them a look yet, this is a good opportunity.

Next, we iterate over the subtasks that are in the subTasks dictionary. For each one, we split off a new thread. The new thread is detached with instructions to call back to the runSubTaskAsynchronouslyWithDictionary: method of the DistributedTask object, which is set as the target, and to pass the subTaskDict dictionary to the method.

After all threads have been launched, a message is sent to the delegate of the class to indicate this. As usual, this message is only sent if there is a delegate set, and the delegate responds to distributedTaskDidLaunchSubTasks:.

The runSubTaskAsynchronouslyWithDictionary: method that each thread calls is something of a monster.


#define ObjectForKeyIsNSNull(dict, key) \
    ([[dict objectForKey:key] isKindOfClass:[NSNull class]])

-(void)runSubTaskAsynchronouslyWithDictionary:(NSDictionary *)subTaskDict {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    @try {
        // Determine the directory where the launch file is
        NSString *launchPath = 
            [subTaskDict objectForKey:LaunchPathKey];
        NSString *launchFileName = 
            [launchPath lastPathComponent];
        NSString *launchPathDir = 
            [launchPath stringByDeletingLastPathComponent];
         
        // Create and setup NSTask for xgrid command
        // Run task in the directory of the launch file
        NSTask *task = [[[NSTask alloc] init] autorelease];
        [task setCurrentDirectoryPath:launchPathDir];
        
        // Create an array with arguments for the NSTask
        NSMutableArray *args = [NSMutableArray arrayWithObjects:
            @"-h", controllerURLString,
            @"-job", @"run",
            nil];
        
        if ( ! ObjectForKeyIsNSNull(subTaskDict, WorkingDirectoryKey) ) {
            [args addObject:@"-in"];
            [args addObject:[subTaskDict objectForKey:WorkingDirectoryKey]];
        }
        
        if ( ! ObjectForKeyIsNSNull(subTaskDict, OutputDirectoryKey) ) {
            [args addObject:@"-out"];
            [args addObject:[subTaskDict objectForKey:OutputDirectoryKey]];
        }
        
        if ( ! ObjectForKeyIsNSNull(subTaskDict, StandardInputKey) ) {
            [args addObject:@"-si"];
            [args addObject:[subTaskDict objectForKey:StandardInputKey]];
        }
        
        if ( ! ObjectForKeyIsNSNull(subTaskDict, StandardOutputKey) ) {
            [args addObject:@"-so"];
            [args addObject:[subTaskDict objectForKey:StandardOutputKey]];
        }
        
        [args addObject:launchFileName];
        
        [task setArguments:args];
        [task setLaunchPath:@"/usr/bin/xgrid"];

        // Launch task, and wait for it to finish
        [task launch];
        [task waitUntilExit];
        int status = [task terminationStatus];
        if (status != 0) NSLog(@"xgrid task failed with status code %d", status);
    }
    @catch (NSException *exception) {
        NSLog(@"An %@ exception was raised in runSubTaskAsynchronouslyWithDictionary: %@", 
            [exception name], [exception reason] );
    }
    @finally {
        [self performSelectorOnMainThread:@selector(subTaskDidFinishWithDictionary:) 
            withObject:subTaskDict waitUntilDone:NO];
        [pool release];
    }
    
}

It begins by initializing an NSAutoreleasePool, and ends by releasing it. You should do this in any method that is called by a new thread. An autorelease pool is created for you in the main thread, but not in threads that you create yourself. If you don't setup an autorelease pool in each new thread, you will get annoying warning messages in the log, and every call to the autorelease method will result in a memory leak.

The rest of the method is embedded in a @try-@catch-@finally block. This is to catch any exceptions that may arise in the worker thread, and which could cause the program to crash. The exception handling here is not very advanced, but it is better than nothing. Basically, if something goes wrong, a log message is printed, and control returned to the main thread as if everything went to plan. It would be better to inform the user of the problem, but we'll leave that to version 2.0. Note that a @finally block gets executed whether an exception is raised or not, so the autorelease pool is always released, preventing potential memory leaks.

runSubTaskAsynchronouslyWithDictionary: simply extracts the data passed to it via the dictionary, and adds it to an array, with each entry coupled to an appropriate xgrid option. For example, the input directory, which becomes the working directory on the agent, is added to the array by first inserting the option string @"-in", followed by the path retrieved from the dictionary. This option is only included if the working directory value in the dictionary is not an instance of the class NSNull.

Pages: 1, 2, 3, 4, 5

Next Pagearrow