Cách dừng NSOperationQueue trong Dispatch_async

Nov 03 2020

Tôi đang thêm nhiều thao tác khối vào một hàng đợi trong vòng lặp for. Trong mỗi thao tác, tôi cần kiểm tra một luồng khác nếu một điều kiện được đáp ứng. Nếu điều kiện được đáp ứng, tất cả các hoạt động sẽ được hủy bỏ.

Tôi đã tạo một mã mẫu để cho bạn thấy vấn đề của tôi:

__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];
            }
        });
    }];
}

Đầu ra mong đợi từ NSLog:

run: 1

Đầu ra tôi nhận được (nó thay đổi từ 7 đến 9):

run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8

Tôi đã tìm kiếm trong nhiều giờ, nhưng tôi không thể tìm ra giải pháp.

Trả lời

Chris Nov 04 2020 at 00:13

Tôi nghĩ rằng tôi đã tìm thấy một giải pháp. Đây là mã được cập nhật:

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

Hãy để tôi sử dụng thêm không gian. Bạn cần đồng bộ hóa quyền truy cập vào biến của mình. Đó là ý tưởng chính xác nhưng bạn đang sử dụng nó không chính xác. Bạn cần một khóa hoặc một ivar nguyên tử hoặc thứ gì đó tương tự để đồng bộ hóa quyền truy cập vào nó.

Sau đó, nếu bạn hủy trong bit send_async, nó sẽ xảy ra sau khi tất cả các khối được thực thi. Đó là những gì đầu ra của bạn hiển thị. Như đã đề cập trong nhận xét, nếu bạn thêm NSLog, ví dụ:

dispatch_async(dispatch_get_main_queue(), ^{
            if ([string isEqualToString:@"1"]) {
                queueDidCancel = YES;
                // Add here
                NSLog(@"Going to cancel now");
                [myQueue cancelAllOperations];
            }

bạn sẽ hiểu ý tôi. Tôi hy vọng rằng điều đó thường thực thi sâu vào mảng của bạn hoặc thậm chí sau khi tất cả mảng thực thi xong.

Nhưng vấn đề lớn nhất là logic của bạn. Bạn cần một số logic để hủy bỏ các khối đó. Chỉ nhắn tin cancelAllOperationshoặc setSuspendedlà không đủ và các khối đã chạy sẽ tiếp tục chạy.

Đây là một ví dụ nhanh.

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];
            }
        });
    }];
}

Bây giờ ví dụ này thực hiện những gì bạn làm nhưng với nhiều khóa hơn và logic hơn một chút và KHÔNG hủyAllOperations cũng không bị treo = YESs. Điều này sẽ không làm những gì bạn muốn vì ngay cả với các chuỗi đang chạy này có xu hướng chạy đến khi hoàn thành và bạn cần logic để dừng nó.

Ngoài ra, trong ví dụ này, tôi để điều kiện thoát hoặc hủy như trong chuỗi chính. Một lần nữa ở đây, điều này có thể có nghĩa là không có gì bị hủy bỏ, nhưng trong cuộc sống thực, bạn thường hủy từ một số giao diện người dùng, ví dụ như một lần nhấp vào nút và sau đó bạn sẽ làm như ở đây. Nhưng bạn có thể hủy ở bất kỳ đâu bằng cách sử dụng khóa.

BIÊN TẬP

Dựa trên nhiều bình luận ở đây là một cách khả thi khác.

Tại đây bạn kiểm tra bên trong khối và dựa vào đó kiểm tra thêm khối khác hay không.

    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];

Ở trên, tôi sử dụng cùng một khối mọi lúc, đây cũng là một giả định nhưng bạn có thể thay đổi nó dễ dàng để thêm các khối khác nhau. Tuy nhiên, nếu tất cả các khối đều giống nhau, bạn sẽ chỉ cần một khối và không cần phải tiếp tục lập lịch cho các khối mới và sau đó có thể thực hiện như bên dưới.

    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;

            } );
        }

    };