1

I'm implementing a tracking mechanism of window A following the position of window B. Window B sending events of its position and window B reacts to those events by calling to setWindowProperties:

void setWindowProperties(bool topMost, bool visible, 
                         CGWindowID parentWindow, CGWindowID aboveWindow, 
                         NSRect windowFrame, NSRect viewFrame, bool isAbove)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        setWindowPropertiesImpl(topMost, visible, parentWindow, aboveWindow, windowFrame, viewFrame, isAbove);
    });
}

But, because of too much events sent by window B I'm getting a "snake tracing" effect. I want to get over it by reacting only to the last position event, meaning, canceling all previous call to :

dispatch_async(dispatch_get_main_queue(), ^{
    setWindowPropertiesImpl(topMost, visible, parentWindow, aboveWindow, windowFrame, viewFrame, isAbove);
});

And as a result, leaving in the queue only the last position event - the only one that matters.

My question: Is there a way to cancel all previous calls for of dispatch_async?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Sanich
  • 1,739
  • 6
  • 25
  • 43

2 Answers2

4

Yes, dispatch tasks are now cancelable, but when there are events that are coming in more quickly than the main queue can process them, it's sometimes useful to use a dispatch source. Specifically a DISPATCH_SOURCE_TYPE_DATA_ADD data source.

// create source (and save this reference somewhere so it doesn't get released on you)

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());

// specify what you want the event handler to do

dispatch_source_set_event_handler(source, ^{
    // whatever you want to do 
});

// start the dispatch source

dispatch_resume(source);

Then, when you want to trigger this, rather than doing dispatch_async, you would:

dispatch_source_merge_data(source, 1);

Clearly, this means that the event handler has to pull the data from the other window rather than pushing it, but hopefully this illustrates the basic idea.


For more information see WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Specifically, see design pattern 8, "Update State Asynchronously" in the latter part of the video.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • from you example. How are you canceling previous calls ? – user2924482 Aug 24 '17 at 21:27
  • If the events are happening quickly, I'd suggest not canceling, but rather use dispatch source add/merge like shown above which will coalesce updates at a speed the UI can handle. But, if you want to cancel a dispatched block of code, see https://stackoverflow.com/a/38372384/1271826 for Swift rendition. For Objective-C rendition, I might refer you to the original WWDC video that demonstrated how to cancel dispatched blocks, [Power, Performance and Diagnostics: What's new in GCD and XPC](https://developer.apple.com/videos/play/wwdc2014/716/), about 20 minutes into the video. – Rob Aug 25 '17 at 05:42
0

You cannot cancel an operation enqueued on a dispatch queue.

GCD queues have no way of cancelling a block once it scheduled. The architecture is very much "fire and forget".

Instead of GCD you could use NSOperationQueue that can also async executes, then you can cancel.

Bannings
  • 10,376
  • 7
  • 44
  • 54
  • Technically, dispatch tasks are now cancelable (see cancelation discussion in [Power, Performance and Diagnostics: What's new in GCD and XPC](https://developer.apple.com/videos/wwdc/2014/?id=716)). – Rob May 25 '15 at 11:06
  • @Rob Cancellation has some important caveats, though: the block isn't released by the time it will be cancelled; Rather that block is still required to be executed by the queue, and when it is cancelled, it will be "ignored" or "bypassed" (most likely the block is embedded in some wrapper function which checks for the cancellation flag). This is important, since only then, when the block has been run and released, captured objects will be released. Note, that queues can be suspended, and may contain cancelled blocks. – CouchDeveloper May 25 '15 at 12:49
  • @CouchDeveloper That behavior is not unique to GCD (e.g. if you use `NSBlockOperation`, you'll see similar behavior; if you use `NSOperation` subclass, that object won't be deallocated at cancellation, but only when queue gets to it). But it doesn't matter: I wasn't trying to make any claims about pros and cons re cancelation in dispatch vs operation queues. (In fact, I think the OP's entire idea of "queue and cancel" for rapid events is inadvisable.) I was merely pointing out that this answer, while historically correct, is no longer strictly true. – Rob May 25 '15 at 15:02