วิธีหยุด NSOperationQueue ระหว่าง dispatch_async

Nov 03 2020

ฉันกำลังเพิ่มการดำเนินการบล็อกจำนวนมากในคิวการดำเนินการในสำหรับลูป ในการดำเนินการแต่ละครั้งฉันต้องตรวจสอบเธรดอื่นว่าเป็นไปตามเงื่อนไขหรือไม่ หากเป็นไปตามเงื่อนไขควรยกเลิกการดำเนินการทั้งหมด

ฉันสร้างโค้ดตัวอย่างเพื่อแสดงปัญหาของฉัน:

__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 เป็นเวลาหลายชั่วโมง แต่หาวิธีแก้ไม่ได้

คำตอบ

Chris Nov 04 2020 at 00:13

ฉันคิดว่าฉันพบทางออกแล้ว นี่คือรหัสที่อัปเดต:

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];
            }
        });
    }];
}
skaak Nov 03 2020 at 23:28

ขอฉันใช้พื้นที่มากขึ้น คุณต้องซิงค์การเข้าถึงตัวแปรของคุณ เป็นความคิดที่ถูกต้อง แต่คุณใช้อย่างไม่ถูกต้อง คุณต้องมีตัวล็อกหรือไอวาร์อะตอมหรืออะไรทำนองนั้นเพื่อซิงค์การเข้าถึง

จากนั้นถ้าคุณยกเลิกในบิต 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;

            } );
        }

    };