วิธีหยุด NSOperationQueue ระหว่าง dispatch_async
ฉันกำลังเพิ่มการดำเนินการบล็อกจำนวนมากในคิวการดำเนินการในสำหรับลูป ในการดำเนินการแต่ละครั้งฉันต้องตรวจสอบเธรดอื่นว่าเป็นไปตามเงื่อนไขหรือไม่ หากเป็นไปตามเงื่อนไขควรยกเลิกการดำเนินการทั้งหมด
ฉันสร้างโค้ดตัวอย่างเพื่อแสดงปัญหาของฉัน:
__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
ฉัน googled เป็นเวลาหลายชั่วโมง แต่หาวิธีแก้ไม่ได้
คำตอบ
ฉันคิดว่าฉันพบทางออกแล้ว นี่คือรหัสที่อัปเดต:
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];
}
});
}];
}
ขอฉันใช้พื้นที่มากขึ้น คุณต้องซิงค์การเข้าถึงตัวแปรของคุณ เป็นความคิดที่ถูกต้อง แต่คุณใช้อย่างไม่ถูกต้อง คุณต้องมีตัวล็อกหรือไอวาร์อะตอมหรืออะไรทำนองนั้นเพื่อซิงค์การเข้าถึง
จากนั้นถ้าคุณยกเลิกในบิต dispatch_async มันจะเกิดขึ้น looooong หลังจากบล็อกทั้งหมดดำเนินการ นั่นคือสิ่งที่ผลลัพธ์ของคุณแสดง ตามที่กล่าวไว้ในความคิดเห็นหากคุณเพิ่ม 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];
}
});
}];
}
ตอนนี้ตัวอย่างนี้ทำในสิ่งที่คุณทำ แต่มีการล็อกเพิ่มขึ้นเล็กน้อยและตรรกะที่เพิ่มขึ้นเล็กน้อยและไม่มีการยกเลิก AllOperations หรือระงับ = ใช่ สิ่งนี้จะไม่ทำในสิ่งที่คุณต้องการแม้ว่าเธรดที่กำลังทำงานอยู่นี้มักจะทำงานจนเสร็จสมบูรณ์และคุณต้องใช้ตรรกะในการหยุด
นอกจากนี้ในตัวอย่างนี้ฉันออกจากเงื่อนไขการออกหรือยกเลิกตามที่อยู่ในเธรดหลัก อีกครั้งที่นี่อาจหมายความว่าไม่มีอะไรถูกยกเลิก แต่ในชีวิตจริงคุณมักจะยกเลิกจาก UI บางอย่างเช่นการคลิกปุ่มแล้วคุณจะทำเช่นนี้ แต่คุณสามารถยกเลิกได้ทุกที่โดยใช้การล็อก
แก้ไข
จากความคิดเห็นจำนวนมากนี่เป็นอีกวิธีหนึ่งที่เป็นไปได้
ที่นี่คุณตรวจสอบภายในบล็อกและขึ้นอยู่กับการตรวจสอบเพิ่มบล็อกอื่นหรือไม่
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;
} );
}
};