I currently have a shell script that process many images one after the other, with the help of GraphicsMagick. It works fine, all calculations are correct, everything works. (that's not a "simple" script, it involves reading dimensions from a JSON file, converting a bunch of images with respect to many constraints).
As we're working with dual-core or quad-core computer, I'd like to parallelize it. And as I'm an iPhone developer liking to introduce myself to Mac development, I'd like to create it with XCode and Objective-C using the "command-line tool" template.
So far so good, but now I'm face with the design of the "task dispatcher" object. I'm fairly lost between running NSTasks in a run loop, in separate threads, using blocks, with or without GCD, with or without ARC.
How would one achieve this? I was thinking of using simple threads to spawn NSTasks, having them report when they're done, and notify my dispatcher's delegate so that it can upgrade its progress bar. But I'd really 开发者_运维知识库like to get in touch with Grand Central Dispatch. Does anyone have any thoughts, ideas, advice about what to do and what not?
Edit: I'm reading Apple's docs, and have found the NSOperationQueue class. Could it be that this is precisely what I'm needing here?
A good class to use to launch independant processes including parameters and environment variables is NSTask. See the documentation for the gory details. Here is a little commandline tool that starts 10 concurrent processes and waits for them to finish. NSOperationQueue would be redundant here because the tasks are already launched concurrently.
-- Edit: Improved Version With Limited Concurrency --
int main (int argc, const char * argv[])
{
@autoreleasepool {
// Let's not have more than 5 parallel processes
dispatch_semaphore_t limit = dispatch_semaphore_create(5);
dispatch_semaphore_t done = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
// Setup the taks as you see fit including the environment variables.
// See docs on NSTask for more on how to use this object.
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/bin/ls";
task.arguments = [NSArray arrayWithObject:@"-la"];
task.terminationHandler = ^(NSTask *task) {
dispatch_semaphore_signal(limit);
if (i==9) dispatch_semaphore_signal(done);
};
dispatch_semaphore_wait(limit, DISPATCH_TIME_FOREVER);
[task launch];
}
dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
dispatch_release(limit);
dispatch_release(done);
}
return 0;
}
-- Original Version --
int main (int argc, const char * argv[])
{
@autoreleasepool {
NSObject *lock = [[NSObject alloc] init];
int __block counter = 10;
for (int i=0; i<10; i++) {
// Setup the taks as you see fit including the environment variables.
// See docs on NSTask for more on how to use this object.
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/bin/ls";
task.arguments = [NSArray arrayWithObject:@"-la"];
task.terminationHandler = ^(NSTask *task) {
@synchronized(lock) { counter--; }
};
[task launch];
}
while (counter)
usleep(50);
[lock release];
}
return 0;
}
In your case you might want to hold the NSTask objects in an array for easier management.
yes - NSOperation
/NSOperationQueue
are good for this task.
i'd start with something like this:
@protocol MONTaskRequestDelegate
- (void)taskRequestDidComplete:(MONTaskRequest *)taskRequest;
@end
@interface MONTaskRequest : NSOperation
{
@private
NSTask * task;
NSObject<MONTaskRequestDelegate>* delegate; /* strong reference. cleared on cancellation and completion, */
}
- (id)initWithTask:(NSTask *)task delegate:(NSObject<MONTaskRequestDelegate>*)delegate;
// interface to access the data from the task you are interested in, whether the task completed, etc.
@end
@implementation MONTaskRequest
// ...
- (void)performDelegateCallback
{
[self.delegate taskRequestDidComplete:self];
self.delegate = nil;
}
- (void)main
{
NSAutoreleasePool * pool = [NSAutoreleasePool new];
[self runTheTask];
// grab what is needed and handle errors
[self performDelegateCallback];
[pool release];
}
- (void)cancel
{
[super cancel];
[self stopTaskIfPossible];
[self performDelegateCallback];
}
@end
then you can use NSOperationQueue
to limit the number of active tasks to a reasonable number.
I use for this purpose the [myObj performSelectorInBackground:@selector(doSomething) withObject:nil]; functionality of an NSObject.
The idea is quite simple: you write a method that does the work, call it from the main thread using the aforementioned method, then call some callback selector if you need to somehow process the results from different threads.
精彩评论