Jak zatrzymać NSOperationQueue podczas dispatch_async
Dodaję wiele operacji blokowych do kolejki operacji w pętli for. W każdej operacji muszę sprawdzić na innym wątku, czy warunek jest spełniony. Jeśli warunek zostanie spełniony, wszystkie operacje powinny zostać anulowane.
Zrobiłem przykładowy kod, aby pokazać Ci mój problem:
__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];
}
});
}];
}
Oczekiwane dane wyjściowe z NSLog:
run: 1
Wynik, który otrzymałem (waha się od 7 do 9):
run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8
Goglowałem godzinami, ale nie mogłem znaleźć rozwiązania.
Odpowiedzi
Myślę, że znalazłem rozwiązanie. Oto zaktualizowany kod:
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];
}
});
}];
}
Pozwól, że wykorzystam więcej miejsca. Musisz zsynchronizować dostęp do swojej zmiennej. To jest poprawny pomysł, ale używasz go nieprawidłowo. Aby zsynchronizować dostęp do niego, potrzebujesz blokady lub atomowego ivar lub czegoś podobnego.
Następnie, jeśli anulujesz w bicie dispatch_async, dzieje się to zbyt długo po wszystkich wykonanych blokach. To właśnie pokazuje twoje wyjście. Jak wspomniano w komentarzu, jeśli dodasz NSLog np
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:@"1"]) {
queueDidCancel = YES;
// Add here
NSLog(@"Going to cancel now");
[myQueue cancelAllOperations];
}
zobaczysz, co mam na myśli. Spodziewam się, że będzie to zwykle wykonywane w głębi twojej tablicy lub nawet po zakończeniu wykonywania całej tablicy.
Ale największym problemem jest twoja logika. Potrzebujesz logiki, aby anulować te bloki. Samo wysyłanie wiadomości cancelAllOperationslub setSuspendednie wystarczy, a już uruchomione bloki będą nadal działać.
Oto szybki przykład.
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];
}
});
}];
}
Teraz ten przykład robi to, co robi twój, ale z nieco większym blokowaniem i nieco większą logiką oraz NO cancellAllOperations ani zawieszone = TAK. To nie zrobi tego, co chcesz, ponieważ nawet w przypadku tych uruchomionych wątków zwykle biegną do końca i potrzebujesz logiki, aby to zatrzymać.
Również w tym przykładzie pozostawiłem warunek wyjścia lub anulowania, tak jak jest w głównym wątku. Ponownie tutaj prawdopodobnie będzie to oznaczać, że nic nie zostanie anulowane, ale w prawdziwym życiu zazwyczaj anulowałbyś z jakiegoś interfejsu użytkownika, np. Kliknięcie przycisku, a następnie zrobiłbyś to tak, jak tutaj. Ale możesz anulować w dowolnym miejscu za pomocą zamka.
EDYTOWAĆ
Na podstawie wielu komentarzy tutaj jest inny możliwy sposób.
Tutaj sprawdzasz wnętrze bloku i na podstawie czeku dodajesz kolejny blok lub nie.
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];
Powyżej za każdym razem używam tego samego bloku, co również jest dość dużym założeniem, ale można go łatwo zmienić, aby dodać inne bloki. Jeśli jednak wszystkie bloki są takie same, potrzebujesz tylko jednego i nie musisz planować nowych bloków, a następnie możesz to zrobić, jak poniżej.
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;
} );
}
};