Как остановить NSOperationQueue во время dispatch_async
Я добавляю много блочных операций в очередь операций цикла for. В каждой операции мне нужно проверять в другом потоке, выполняется ли условие. Если условие выполнено, все операции следует отменить.
Я сделал образец кода, чтобы показать вам свою проблему:
__block BOOL queueDidCancel = NO;
NSArray *array = [NSArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
if (queueDidCancel) {return;}
NSLog(@"run: %@", string);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([string isEqualToString:@"1"]) {
queueDidCancel = YES;
[myQueue cancelAllOperations];
}
});
}];
}
Ожидаемый результат NSLog:
run: 1
Получился результат (от 7 до 9):
run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8
Я гуглил часами, но решения не нашел.
Ответы
Думаю, я нашел решение. Вот обновленный код:
NSArray *array = [NSArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
[myQueue setSuspended:YES];
NSLog(@"run: %@", string);
dispatch_async(dispatch_get_main_queue(), ^{
if (![string isEqualToString:@"1"]) {
[myQueue setSuspended:NO];
}
});
}];
}
Позвольте мне использовать больше места. Вам необходимо синхронизировать доступ к вашей переменной. Это правильная идея, но вы ее неправильно используете. Вам нужен замок или атомарный ivar или что-то в этом роде, чтобы синхронизировать доступ к нему.
Затем, если вы отмените в бите dispatch_async, это произойдет слишком долго после выполнения всех блоков. Это то, что показывает ваш вывод. Как упоминалось в комментарии, если вы добавите NSLog, например
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:@"1"]) {
queueDidCancel = YES;
// Add here
NSLog(@"Going to cancel now");
[myQueue cancelAllOperations];
}
вы поймете, что я имею в виду. Я ожидаю, что это обычно будет выполняться глубоко в вашем массиве или даже после того, как весь массив завершит выполнение.
Но самая большая проблема - это ваша логика. Вам нужна логика, чтобы отменить эти блоки. Просто обмен сообщениями cancelAllOperationsили setSuspendedэтого недостаточно, и блоки, которые уже запущены, будут продолжать работать.
Вот небольшой пример.
NSObject * lock = NSObject.new; // Use this to sync access
__block BOOL queueDidCancel = NO;
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
// Here you also need to add some logic, e.g. as below
// Note the sync access
@synchronized ( lock ) {
if (queueDidCancel) { break; }
}
[myQueue addOperationWithBlock:^{
// You need to sync access to queueDidCancel especially if
// you access it from main and the queue or if you increase
// the concurrent count
// This lock is one way of doing it, there are others
@synchronized ( lock ) {
// Here is your cancel logic! This is fine here
if (queueDidCancel) {return;}
}
NSLog(@"run: %@", string);
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:@"1"]) {
// Again you need to sync this
@synchronized ( lock ) {
queueDidCancel = YES;
}
// This is not needed your logic should take care of it ...
// The problem is that running threads will probably
// keep on running and you need logic to stop them
// [myQueue cancelAllOperations];
}
});
}];
}
Теперь этот пример делает то же, что и ваш, но с немного большей блокировкой и немного большей логикой, и НЕТ cancelAllOperations или приостановлено = ДА. Это не будет делать то, что вы хотите, поскольку даже при этом запущенные потоки, как правило, завершаются, и вам нужна логика, чтобы остановить его.
Кроме того, в этом примере я оставил условие выхода или отмены, как и в основном потоке. Опять же, здесь это, вероятно, будет означать, что ничего не отменяется, но в реальной жизни вы обычно отмените действие в каком-либо пользовательском интерфейсе, например, нажатием кнопки, а затем сделаете это, как здесь. Но вы можете отменить где угодно, используя блокировку.
РЕДАКТИРОВАТЬ
Основываясь на большом количестве комментариев, есть еще один возможный способ.
Здесь вы проверяете внутри блока и на основе проверки добавляете еще один блок или нет.
NSOperationQueue * queue = NSOperationQueue.new;
// Important
queue.maxConcurrentOperationCount = 1;
void ( ^ block ) ( void ) = ^ {
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means continue adding blocks
if ( cond )
{
[queue addOperationWithBlock:block];
}
// else done
} );
};
// Start it all
[queue addOperationWithBlock:block];
Выше я использую один и тот же блок каждый раз, что также является предположением, но вы можете легко изменить его, добавив разные блоки. Однако, если блоки все одинаковые, вам понадобится только один, и вам не нужно будет планировать новые блоки, а затем вы можете сделать это, как показано ниже.
void ( ^ block1 ) ( void ) = ^ {
// Some logic
__block BOOL done = NO;
while ( ! done )
{
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means stop! here
done = cond;
} );
}
};