Подтвердить что ты не робот

NSOperation - принудительная операция, чтобы ждать других динамически

Я пытаюсь реализовать очередь операций, и у меня есть следующий сценарий:

NSOperation A
NSOperation B
NSOperation C
NSOperation D
NSOperationQueue queue

Я начинаю добавлять A в queue.

Во время выполнения A мне нужно получить некоторые данные из B, и я не могу продолжить с A, пока B не вернет мне то, что мне нужно.

Такая же ситуация будет иметь место для B в зависимости от C и для C в зависимости от D.

Чтобы справиться с этим, на каждом NSOperation у меня есть этот код:

NSOperation *operation; //This can be A, B, C, D or any other NSOperation

[self setQueuePriority:NSOperationQueuePriorityVeryLow]; //Set the current NSOperation with low priority

[queue addOperation: operation]; //Add the operation that I want to the queue

while(!operation.isFinished && !self.isCancelled){} //I need to wait the operation that I depend before moving on with the current operation

[self setQueuePriority:NSOperationQueuePriorityNormal]; //After the while, the other operation finished so I return my priority to normal and continue

if(self.isCancelled){ //If I get out of the while because the current operation was cancelled I also cancel the other operation.
[operation cancel];          
}

Моя проблема в том, что когда у меня есть что-то вроде 3 или 4 NSOperations ожидания и выполнения while(!operacao.isFinished && !self.isCancelled){}, мой код просто замирает, потому что NSOperation, который для меня важен, не выполняется, даже если он имеет более высокий приоритет.

Что я пробовал

  • Добавление зависимости во время выполнения, но поскольку мой NSOperation уже запущен, я не вижу никакого эффекта.

  • Вместо добавления операции в очередь я могу что-то сделать [operation start]. Он работает, но отмена текущей операции также отменяет другие операции, которые я начал?

  • Я могу сделать что-то вроде while(!operacao.isFinished && !self.isCancelled){[NSThread sleepForTimeInterval:0.001];}. Это работает, но верно ли это? Может быть, есть лучшее решение.

В этой ситуации я могу гарантировать, что операция, которую я хочу, будет запущена, а остальные будут ждать в фоновом режиме? Каков правильный способ решить эту проблему?

Если кто-нибудь спросит меня, почему я не добавляю зависимость, прежде чем запускать свою очередь, потому что операция будет нужна другой, только если некоторые условия верны. Я буду знать, нужна ли мне другая операция только во время выполнения.

Спасибо за ваше время.

4b9b3361

Ответ 1

Вот две идеи для вас с надуманными примерами. Я использовал только две операции, но вы могли бы расширять концепцию до любого числа и/или устанавливать их по мере необходимости.

Пример 1: Использование Grand Central Dispatch

GCD предоставляет легкие "группы отправки", которые позволяют вам явно заказывать задачи, а затем ждать их завершения. В этом случае AlphaOperation создает группу и вводит ее, затем запускает BetaOperation, чей completionBlock заставляет группу оставаться. Когда вы вызываете dispatch_group_wait, текущий поток блокируется до тех пор, пока количество входящих в группу значений не будет равно количеству оставшихся времени (что бы было похоже на счет сохранения). Не забудьте проверить состояние isCancelled операции после любой потенциально долговременной задачи.

@interface BetaOperation : NSOperation
@end
@implementation BetaOperation
- (void)main
{
    NSLog(@"beta operation finishing");
}
@end

@interface AlphaOperation : NSOperation
@end
@implementation AlphaOperation
- (void)main
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);

    BetaOperation *betaOperation = [[BetaOperation alloc] init];
    betaOperation.completionBlock = ^{
        dispatch_group_leave(group);
    };

    [betaOperation start];

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    if ([self isCancelled])
        return;

    NSLog(@"alpha operation finishing");
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    dispatch_async(dispatch_get_main_queue(), ^{
        AlphaOperation *operation = [[AlphaOperation alloc] init];
        [operation start];
    });

    return YES;
}

@end

Пример 2: Использование локального NSOperationQueue

Поскольку вы уже работаете с рабочими операциями, другой параметр создает очередь как свойство AlphaOperation, а затем добавляет BetaOperation и вызывает waitUntilAllOperationsAreFinished в очереди. Это дает дополнительное преимущество в том, что вы можете легко отменить операции очереди, когда AlphaOperation отменяется, просто переопределив метод cancel.

@interface BetaOperation : NSOperation
@end
@implementation BetaOperation
- (void)main
{
    NSLog(@"beta operation finishing");
}
@end

@interface AlphaOperation : NSOperation
@property (strong) NSOperationQueue *queue;
@end
@implementation AlphaOperation
- (void)main
{
    self.queue = [[NSOperationQueue alloc] init];

    BetaOperation *betaOperation = [[BetaOperation alloc] init];
    [self.queue addOperation:betaOperation];
    [self.queue waitUntilAllOperationsAreFinished];

    if ([self isCancelled])
        return;

    NSLog(@"alpha operation finishing");
}

- (void)cancel
{
    [super cancel];

    [self.queue cancelAllOperations];
}
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    dispatch_async(dispatch_get_main_queue(), ^{
        AlphaOperation *operation = [[AlphaOperation alloc] init];
        [operation start];
    });

    return YES;
}

@end

Ответ 2

Один из подходов состоит в том, чтобы управлять этим извне классов операций, т.е. правильно настройте зависимости операции между A/B/C/D при их создании.

Шаги: (В методе, который создает эти операции)

1) Создать операцию A

2) Если данные, предоставленные операцией В, недоступны, создайте операцию В и сделайте операцию А зависимой от операции В. то есть. что-то вроде operationA.addDependency(operationB);

3). повторите шаг 2 для C и D. (то есть B зависит от C и C зависит от D, если требуется)

4) Добавьте операции в очередь. Очередь будет выполняться на основе зависимостей, т.е. D, C, B, A.

Ответ 3

Попробуйте использовать setCompletionBlock: следующим образом:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *operationA;
NSOperation *operationB;

//... initialize operationA and operationB however you please ...

[operationA setCompletionBlock:^{
    if ([operationA satisfiesSomeCriteria]) {
        [queue addOperation:operationB];
    }
}];

[queue addOperation:operationA];

Когда вы устанавливаете блок завершения операции, он выполняется после завершения или отмены основной задачи операции. Таким образом, результаты работы, выполняемой операцией, доступны, чтобы вы могли решить, должна ли следующая операция быть добавлена ​​в очередь.

Ответ 4

Итак, в основном вам просто нужно убедиться, что первый заканчивается до начала следующего? NSOperationQueue будет работать параллельно, если вы не скажете об этом. Вы можете вызвать setMaxConcurrentOperationCount: в очереди на работу и установить его в один, чтобы в основном превратить его в последовательную очередь, в которой одновременно будет выполняться только одна операция.

Ответ 5

Как только NSOperation находится в основном методе, вам нужно пройти через него. Состояние приостановлено, только закончено или отменено.

Я бы выполнил NSCopying на операции A, которая скопировала бы все состояние в новый экземпляр. У вас должен быть метод или блок делегата, способный сообщить, что эта операция не может пройти, потому что ей не хватает информации из операции B.

Итак, процесс будет таким:

  • Создать операцию A, установить делегат
  • вы не можете продолжить, делегировать метод fire
  • делегат создает новую операцию B, создает копию операции A, устанавливает зависимость так, что A будет ждать завершения B
  • то делегат отменяет оригинал op A

Внутри делегата вы должны приостановить очередь, чтобы избежать состояния гонки. После вышеуказанных шагов вы возобновите очередь. В операции A у вас будет несколько мест, где вы проверяете isCancelled, чтобы на самом деле больше не работать в основном, когда он был отменен.

Ответ 6

Я думаю, что вы используете неправильный подход. Если каждая операция в очереди имеет приоритет, и они должны выполняться по порядку, почему бы не использовать 4 разных потока?
Возьмите ivar, который представляет состояние (0: операция не завершена, 1: одна операция завершена и т.д.), Защитите ее с условием:

@property(nonatomic,strong) NSCondition* condition;
@property (nonatomic) NSUInteger state; 

Инициализировать все (состояние начинается с нуля), а затем создать 4 разных потока с разными приоритетами. Это пример для селектора, выполняемого потоком A:

- (void) threadA : (id) sender
{
    [condition lock];
    while(state!=3)
    {
        [condition wait];
    }
    // Do the job here
    state=4; // That kinda useless but useful if in future you
    // want another thread that starts doing the job when A ends
    [condition unlock];
}

Таким образом, все выполняется в том порядке, в котором вы хотите.

EDIT

Вы можете сделать эквивалент, который я здесь сделал, но используя NSOperationQueue:

NSOperationQueue* queue=[NSOperationQueue new];
[queue setMaxConcurrentOperationCount: 4];
[queue addOperation: [[NSInvocationOperation alloc]initWithTarget: self selector: @selector(threadA:) object: nil]]

Говоря, что вы выполняете неправильный подход, я имею в виду, что вы не должны использовать очередь с 1 как maxConcurrentOperationCount. Основная очередь имеет это значение, равное 1, и что причина ваших проблем.

Ответ 7

Как вы нашли, вы не можете делать это с помощью зависимостей, потому что это влияет только на запуск операции - и если вы не знаете, что вам понадобятся вспомогательные операции до тех пор, пока основной не будет запущен, не хорошо. Вы не можете решить эту проблему с помощью одной очереди операций.

Однако, поскольку вы уже работаете в очереди операций, нет необходимости добавлять дополнительные операции в очередь. Просто выполняйте их синхронно на месте. Вы должны ждать, пока они вернутся, так почему бы и нет?