So stoppen Sie NSOperationQueue während dispatch_async
Ich füge einer Operationswarteschlange in einer for-Schleife viele Blockoperationen hinzu. Bei jeder Operation muss ich einen anderen Thread überprüfen, ob eine Bedingung erfüllt ist. Wenn die Bedingung erfüllt ist, sollten alle Vorgänge abgebrochen werden.
Ich habe einen Beispielcode erstellt, um Ihnen mein Problem zu zeigen:
__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];
}
});
}];
}
Erwartete Ausgabe von NSLog:
run: 1
Ausgabe, die ich bekommen habe (sie variiert zwischen 7 und 9):
run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8
Ich habe stundenlang gegoogelt, aber ich konnte keine Lösung finden.
Antworten
Ich glaube, ich habe eine Lösung gefunden. Hier ist der aktualisierte Code:
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];
}
});
}];
}
Lassen Sie mich mehr Platz nutzen. Sie müssen den Zugriff auf Ihre Variable synchronisieren. Es ist die richtige Idee, aber Sie verwenden sie falsch. Sie benötigen ein Schloss oder einen atomaren Ivar oder ähnliches, um den Zugriff darauf zu synchronisieren.
Wenn Sie dann das dispatch_async-Bit abbrechen, geschieht dies ziemlich lange, nachdem alle Blöcke ausgeführt wurden. Das zeigt Ihre Ausgabe. Wie im Kommentar erwähnt, wenn Sie ein NSLog hinzufügen, z
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:@"1"]) {
queueDidCancel = YES;
// Add here
NSLog(@"Going to cancel now");
[myQueue cancelAllOperations];
}
Du wirst sehen, was ich meine. Ich gehe davon aus, dass dies normalerweise tief in Ihrem Array ausgeführt wird oder sogar, nachdem das gesamte Array vollständig ausgeführt wurde.
Das größte Problem ist jedoch Ihre Logik. Sie benötigen eine Logik, um diese Blöcke abzubrechen. Nur Nachrichten cancelAllOperationsoder setSuspendednicht genug und Blöcke, die bereits ausgeführt werden, werden weiter ausgeführt.
Hier ist ein kurzes Beispiel.
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];
}
});
}];
}
Jetzt macht dieses Beispiel das, was Sie tun, aber mit etwas mehr Sperren und etwas mehr Logik und NO cancelAllOperations noch suspendiert = YESs. Dies wird nicht das tun, was Sie wollen, da selbst mit diesen laufenden Threads die Ausführung zum Abschluss führt und Sie Logik benötigen, um sie zu stoppen.
Auch in diesem Beispiel habe ich die Exit- oder Cancel-Bedingung wie im Haupt-Thread belassen. Auch hier bedeutet dies wahrscheinlich, dass nichts abgebrochen wird, aber im wirklichen Leben würden Sie normalerweise von einer Benutzeroberfläche abbrechen, z. B. durch Klicken auf eine Schaltfläche, und dann würden Sie es wie hier tun. Mit dem Schloss können Sie jedoch überall abbrechen.
BEARBEITEN
Basierend auf vielen Kommentaren ist hier ein anderer möglicher Weg.
Hier checken Sie innerhalb des Blocks ein und fügen basierend auf dem Check einen weiteren Block hinzu oder nicht.
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];
Oben verwende ich jedes Mal den gleichen Block, was ebenfalls eine gute Annahme ist, aber Sie können ihn leicht ändern, um verschiedene Blöcke hinzuzufügen. Wenn die Blöcke jedoch alle gleich sind, benötigen Sie nur einen und müssen keine neuen Blöcke mehr planen. Anschließend können Sie dies wie folgt tun.
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;
} );
}
};