Dispatch_async sırasında NSOperationQueue nasıl durdurulur
Bir for döngüsündeki bir işlem kuyruğuna birçok blok işlemi ekliyorum. Her işlemde, bir koşulun yerine getirilip getirilmediğini başka bir iş parçacığı üzerinde kontrol etmem gerekiyor. Koşul yerine getirilirse, tüm işlemler iptal edilmelidir.
Size sorunumu göstermek için örnek bir kod hazırladım:
__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'dan beklenen çıktı:
run: 1
Aldığım çıktı (7 ile 9 arasında değişir):
run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8
Saatlerce Google'da araştırdım ama bir çözüm bulamadım.
Yanıtlar
Sanırım bir çözüm buldum. İşte güncellenmiş 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];
}
});
}];
}
Daha fazla alan kullanmama izin verin. Değişkeninize erişimi eşitlemeniz gerekir. Doğru fikir ama sen yanlış kullanıyorsun. Erişimi senkronize etmek için bir kilide veya atomik bir ivar'a veya bunun gibi bir şeye ihtiyacınız var.
Daha sonra dispatch_async bitinde iptal ederseniz, tüm bloklar yürütüldükten sonra çoooook uzun süre gerçekleşir. Çıktınızın gösterdiği şey budur. Yorumda belirtildiği gibi, bir NSLog eklerseniz, örneğin
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:@"1"]) {
queueDidCancel = YES;
// Add here
NSLog(@"Going to cancel now");
[myQueue cancelAllOperations];
}
ne demek istediğimi anlayacaksın. Bunun tipik olarak dizinizin derinliklerinde veya tüm dizinin yürütülmesi bittikten sonra bile çalışmasını bekliyorum.
Ama en büyük problem senin mantığın. Bu blokları iptal etmek için biraz mantığa ihtiyacınız var. Sadece mesajlaşma cancelAllOperationsveya setSuspendedyeterli değil ve zaten çalışmakta olan bloklar çalışmaya devam edecektir.
İşte hızlı bir örnek.
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];
}
});
}];
}
Şimdi bu örnek sizin yaptığınız şeyi yapar, ancak biraz daha fazla kilitleme ve biraz daha mantık ve NO cancelAllOperations veya suspended = YESs ile. Bu, devam eden bu iş parçacıkları tamamlanma eğiliminde olduğu ve onu durdurmak için mantığa ihtiyacınız olduğu için, istediğinizi yapmayacaktır.
Ayrıca, bu örnekte, ana iş parçacığında olduğu gibi çıkış veya iptal koşulunu bıraktım. Yine burada bu muhtemelen hiçbir şeyin iptal edilmeyeceği anlamına gelecektir, ancak gerçek hayatta tipik olarak bazı kullanıcı arayüzlerinden iptal edersiniz, örneğin bir düğme tıklar ve sonra bunu burada olduğu gibi yaparsınız. Ancak kilidi kullanarak her yerde iptal edebilirsiniz.
DÜZENLE
Pek çok yoruma dayalı olarak burada başka bir olası yol var.
Burada bloğun içini kontrol edersiniz ve kontrole göre başka bir blok ekleyip eklemeyin.
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];
Yukarıda her seferinde aynı bloğu kullanıyorum ki bu da oldukça büyük bir varsayımdır ancak farklı bloklar eklemek için kolayca değiştirebilirsiniz. Bununla birlikte, blokların tümü aynıysa, yalnızca birine ihtiyacınız olacak ve yeni blokları programlamaya devam etmenize gerek kalmayacak ve ardından aşağıdaki gibi yapabilirsiniz.
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;
} );
}
};