Шаблон проектирования Obj-C: средство запуска параллельных задач - PullRequest
1 голос
/ 06 сентября 2011

В настоящее время у меня есть сценарий оболочки, который обрабатывает множество изображений одно за другим с помощью GraphicsMagick. Работает нормально, все расчеты верны, все работает. (это не «простой» сценарий, он включает чтение измерений из файла JSON, преобразование группы изображений с учетом многих ограничений).

Поскольку мы работаем с двухъядерным или четырехъядерным компьютером, я бы хотел распараллелить его. И так как я являюсь разработчиком для iPhone и хочу познакомиться с разработкой для Mac, я бы хотел создать его с помощью XCode и Objective-C, используя шаблон «инструмента командной строки».

Пока все хорошо, но теперь я столкнулся с дизайном объекта "диспетчер задач". Я довольно потерян между выполнением NSTasks в цикле выполнения, в отдельных потоках, с использованием блоков, с GCD или без, с ARC или без.

Как можно этого достичь? Я думал об использовании простых потоков для порождения NSTasks, чтобы они сообщали, когда они закончили, и уведомляли делегата моего диспетчера, чтобы он мог обновить свой индикатор выполнения. Но мне бы очень хотелось связаться с Grand Central Dispatch. У кого-нибудь есть мысли, идеи, советы о том, что делать, а что нет?

Редактировать: я читаю документы Apple и обнаружил класс NSOperationQueue. Может быть, это точно , что мне здесь нужно?

Ответы [ 3 ]

3 голосов
/ 06 сентября 2011

Хорошим классом для запуска независимых процессов, включая параметры и переменные среды, является NSTask. Смотрите документацию для кровавых деталей. Вот небольшой инструмент командной строки, который запускает 10 параллельных процессов и ожидает их завершения. NSOperationQueue будет избыточным здесь, потому что задачи уже запускаются одновременно.

- Правка: улучшенная версия с ограниченным параллелизмом -

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;

}

- Оригинальная версия -

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;
}

В вашем случае вы можете захотеть хранить объекты NSTask в массиве для более простого управления.

1 голос
/ 06 сентября 2011

да - NSOperation / NSOperationQueue хороши для этой задачи.

Я бы начал с чего-то вроде этого:

@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

, затем вы можете использовать NSOperationQueue, чтобы ограничить количество активных задач разумным числом.

0 голосов
/ 06 сентября 2011

Я использую для этой цели [myObj executeSelectorInBackground: @selector (doSomething) withObject: nil]; функциональность NSObject.

Идея довольно проста: вы пишете метод, который выполняет работу, вызываете его из основного потока, используя вышеупомянутый метод, а затем вызываете какой-то селектор обратного вызова, если вам нужно каким-то образом обработать результаты из разных потоков.

...