Search code examples
swiftgrand-central-dispatchcancellationdispatchworkitem

How to stop a DispatchWorkItem in GCD?


I am currently playing around with Grand Central Dispatch and discovered a class called DispatchWorkItem. The documentation seems a little incomplete so I am not sure about using it the right way. I created the following snippet and expected something different. I expected that the item will be cancelled after calling cancel on it. But the iteration continues for some reason. Any ideas what I am doing wrong? The code seems fine for me.

@IBAction func testDispatchItems() {
    let queue = DispatchQueue.global(attributes:.qosUserInitiated)
    let item = DispatchWorkItem { [weak self] in
        for i in 0...10000000 {
            print(i)
            self?.heavyWork()
        }
    }

    queue.async(execute: item)
    queue.after(walltime: .now() + 2) {
        item.cancel()
    }
}

Solution

  • GCD does not perform preemptive cancelations. So, to stop a work item that has already started, you have to test for cancelations yourself. In Swift, cancel the DispatchWorkItem. In Objective-C, call dispatch_block_cancel on the block you created with dispatch_block_create. You can then test to see if was canceled or not with isCancelled in Swift (known as dispatch_block_testcancel in Objective-C).

    func testDispatchItems() {
        let queue = DispatchQueue.global()
    
        var item: DispatchWorkItem?
    
        // create work item
    
        item = DispatchWorkItem { [weak self] in
            for i in 0 ... 10_000_000 {
                if item?.isCancelled ?? true { break }
                print(i)
                self?.heavyWork()
            }
            item = nil    // resolve strong reference cycle of the `DispatchWorkItem`
        }
    
        // start it
    
        queue.async(execute: item!)
    
        // after five seconds, stop it if it hasn't already
    
        queue.asyncAfter(deadline: .now() + 5) {
            item?.cancel()
            item = nil
        }
    }
    

    Or, in Objective-C:

    - (void)testDispatchItem {
        dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    
        static dispatch_block_t block = nil;  // either static or property
    
        __weak typeof(self) weakSelf = self;
    
        block = dispatch_block_create(0, ^{
            for (long i = 0; i < 10000000; i++) {
                if (dispatch_block_testcancel(block)) { break; }
                NSLog(@"%ld", i);
                [weakSelf heavyWork];
            }
    
            block = nil;
        });
    
        // start it
    
        dispatch_async(queue, block);
    
        // after five seconds, stop it if it hasn't already
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            if (block) { dispatch_block_cancel(block); }
        });
    }