Зависимость NSOperation и завершение блока

У нас есть простая проблема с NSOperationQueue, вот простая логика работы:

self.queue = [[NSOperationQueue alloc] init];

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation A");
    [NSThread sleepForTimeInterval:1.2];
    NSLog(@"- Done operation A");
}];

NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation B");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"- Done operation B");
}];

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
}];

[operationB setCompletionBlock:^{
    NSLog(@"-- Completion Block B");
}];

[operationB addDependency:operationA];
[self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];

Вот окончательный вывод

2015-12-21 14:59:57.463 SampleProject[18046:310901] - Running operation A
2015-12-21 14:59:58.664 SampleProject[18046:310901] - Done operation A
2015-12-21 14:59:58.664 SampleProject[18046:310900] - Running operation B
2015-12-21 14:59:58.664 SampleProject[18046:310904] -- Completion Block A
2015-12-21 15:00:00.736 SampleProject[18046:310900] - Done operation B
2015-12-21 15:00:00.736 SampleProject[18046:310904] -- Completion Block B

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

Как мне заставить операцию B выполняться после всех блоков завершения операции A?


person Marc-Alexandre Bérubé    schedule 21.12.2015    source источник
comment
Возможен ли переход на GCD? В этом API есть все необходимые инструменты. В этом конкретном случае барьерные блоки - это то, что вам нужно. С их помощью вы можете запускать множество задач типа A параллельно, а затем добавлять барьерную задачу типа B, которая не может начаться до тех пор, пока не будут завершены все A, и никакая новая задача не может начаться до завершения B.   -  person pco494    schedule 22.12.2015
comment
Не могли бы вы привести краткий пример того, как это будет работать? Я немного разбираюсь в GCD, но недостаточно для такого рода задач   -  person Marc-Alexandre Bérubé    schedule 22.12.2015
comment
Честно говоря, я тоже не эксперт, но я прочитал туториал Рэя Вендерлиха, там на днях и в середине части 1 Обращение с читателями и писателями Проблема обсуждается, что-то, что звучит точно так же, как то, что вы ищете.   -  person pco494    schedule 22.12.2015
comment
Можно сделать с GCD, как можно сделать все, что угодно, написав Ассемблер. NSOperationQueue построен на GCD. Только реализовать его намного сложнее. Попробуйте отменить зависимые блоки в dispatch_queues. Бесплатно с NSOperations! Попробуйте изменить приоритеты операций с помощью API-интерфейсов GCD, не обжегшись. Барьер фактически останавливает поток, в котором выполняется блок, в то время как NSOperation делает это намного легче, используя KVC/KVO, не становясь готовым к выполнению, пока операции, от которых вы зависите, не будут завершены. dispatch_queues продолжают работать без барьеров. НСООперации рулят!   -  person Motti Shneor    schedule 11.10.2020


Ответы (3)


Как вы обнаружили в своем тестировании, блоки завершения не являются «частью очереди», а выполняются вне очереди операций (и в другом потоке). Таким образом, завершение блока операции А будет выполняться одновременно с операцией Б.

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

Вы говорите, что используете completeBlocks для передачи информации из операции A в B, для этого есть два варианта: дать B ссылки на все A (не слабые), чтобы при запуске B он мог выбирать результаты из всех A. Или, если по какой-то причине невозможно сохранить все A до тех пор, пока B не запустится, воссоздайте свой завершениеBlock как другую NSOperation:

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    // do stuff
}];

NSOperation *operationATail = [NSBlockOperation blockOperationWithBlock:^{
    // do completionBlock stuff
}];

[operationATail addDependency:operationA];
[operationB addDependency:operationATail];
[self.queue addOperations:@[operationA, operationATail, operationB] waitUntilFinished:NO];
person alexkent    schedule 02.01.2016
comment
Хорошо описано. Это именно то, что я в итоге сделал (фактически я создал собственные подклассы NSOperation поверх этих уже существующих операций). Должен сказать, что так намного чище. Я забыл объяснить в своем ответе, поэтому спасибо за объяснение. - person Marc-Alexandre Bérubé; 04.01.2016
comment
Зачем вам нужен подкласс NSOperation? вводя зависимость, вы ЗНАЕТЕ абсолютно точно, что все зависимые операции завершились успешно ДО начала вашей другой операции. Следовательно, они могут просто совместно использовать объект данных для передачи необходимой информации, а более поздняя операция может предполагать, что первая оставила нужную информацию. здесь вам не нужны подклассы или какие-либо триггеры - по крайней мере, для описанной проблемы. - person Motti Shneor; 11.10.2020

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

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"- Running operation B");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"- Done operation B");
    }];
    [queue addOperations:@[operationB] waitUntilFinished:NO];
}];


[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A when we dont need B");
}];

Вместо этого есть несколько более приятных способов выполнить это, используя

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     [self operationB];
}
person Reedy    schedule 21.12.2015
comment
Потому что, как уже упоминалось, у меня может быть несколько операций A и только одна операция B. Мне не нужна 1 операция B на операцию A, имеет ли это смысл? - person Marc-Alexandre Bérubé; 21.12.2015
comment
Так почему бы не установить блок завершения по-другому, когда вам нужно. Поэтому каждый раз, когда вам нужно запустить операцию b после завершения операции a, установите блок завершения операции a в соответствии с вашими потребностями. - person Reedy; 21.12.2015

  1. Избегайте блоков завершения — это механизм вне очереди, непригодный для синхронизации каких-либо операций или их связи.

  2. Введение зависимости (B зависит от A) означает, что B будет работать только ПОСЛЕ успешного завершения A.

  3. По этой причине — любой простой объект данных может быть использован для безопасной передачи информации между этими двумя операциями — который мог бы просто поделиться ею (достаточно создать его вне блоков, определяющих обе операции. Когда B запускается, он может ПРЕДПОЛОЖИТЬ, что A имеет уже поместили необходимую информацию в объект данных и просто получили к ней доступ.

    self.queue = [[NSOperationQueue alloc] init];
    
    NSMutableDictionary *info = [NSMutableDictionary new]; // data object for communication
    
    NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"- Running operation A");
     [NSThread sleepForTimeInterval:1.2];
    
      info[@"DoThis"] = @YES;
      info[@"DoThat"] = @NO;
      NSLog(@"- Done operation A");
    }];
    
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"- Running operation B");
    
      if ([info[@"DoThis"] boolValue] NSLog(@"Op A said to do this.");
      if ([info[@"DoThat"] boolValue] NSLog(@"Op A said to do that.");
    
      [NSThread sleepForTimeInterval:2];
      NSLog(@"- Done operation B");
    }];
    
    [operationB addDependency:operationA];
    [self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];
    
person Motti Shneor    schedule 11.10.2020