Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dispatch queues: How to tell if they're running and how to stop them

I'm just playing around with GCD and I've written a toy CoinFlipper app.

Here's the method that flips the coins:

- (void)flipCoins:(NSUInteger)nFlips{

    // Create the queues for work
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);

    // Split the number of flips into whole chunks of kChunkSize and the remainder.
    NSUInteger numberOfWholeChunks = nFlips / kChunkSize;
    NSUInteger numberOfRemainingFlips = nFlips - numberOfWholeChunks * kChunkSize;

    if (numberOfWholeChunks > 0) {
        for (NSUInteger index = 0; index < numberOfWholeChunks; index++) {
            dispatch_async(queue, ^{
                NSUInteger h = 0;
                NSUInteger t = 0;
                flipTheCoins(kChunkSize, &h, &t);
                dispatch_async(mainQueue, ^{
                    self.nHeads += h;
                    self.nTails += t;
                });
            });
        }
    }
    if (numberOfRemainingFlips > 0) {
        dispatch_async(queue, ^{
            NSUInteger h = 0;
            NSUInteger t = 0;
            flipTheCoins(numberOfRemainingFlips, &h, &t);
            dispatch_async(mainQueue, ^{
                self.nHeads += h;
                self.nTails += t;
            });
        });

    }
}

As you can see; I'm breaking the number of flips into large chunks flipping them in the background and updating properties in the main queue. The properties are being observed by the window controller and an UI is updated with the running results.

I've looked through the Concurrency Programming Guide and the GCD docs, and although there is a way to suspend a queue, there isn't a way to stop them, and remove all queued and not running objects.

I'd like to be able to hook up a 'stop' button to cancel flipping once it's started. With NSOperationQueue I can observe the operationCount property to know if it's running, and cancelAllOperations to remove queued blocks.

I've looked through the Concurrency Programming Guide and the GCD docs, and although there is a way to suspend a queue, there isn't a way to stop them, and remove all queued and not running objects.

So :-

  1. How do I tell if blocks I've added to a queue are still waiting?
  2. How do I cancel blocks that haven't run yet?
  3. I'm new to the GCD stuff, so am I doing it right?
like image 467
Abizern Avatar asked Oct 11 '09 13:10

Abizern


People also ask

How do I find the current dispatch queue?

To get the dispatch queue object you can use [NSOperationQueue currentQueue]. underlyingQueue , which returns your currrent queue as a dispatch_queue_t . - works for main queue!

How does queue dispatch work?

Dispatch queues are FIFO queues to which your application can submit tasks in the form of block objects. Dispatch queues execute tasks either serially or concurrently. Work submitted to dispatch queues executes on a pool of threads managed by the system.

How many types of dispatch queues are there?

There are two types of dispatch queues, serial dispatch queues and concurrent dispatch queues.

What is difference between operation queue and dispatch queue?

Here are how operation queues are different from dispatch queues: In operation queues, you can set priority for your operations and also you can add dependencies to the operations which means you can define that some operation execute only after the completion of other operations.


3 Answers

This is a semi-common question when programming with GCD.

The short answer is that GCD does not have a cancelation API for queues. The rationale:

  1. memory management would become vastly more complicated, because a given block might be responsible for free()ing a given allocation of memory. By always running the block, GCD ensures that memory management remains easy.
  2. It is practically impossible to halt a running block without corrupting state.
  3. Most code that needs cancellation logic is already tracking that state in private data structures.

Given all of these cases, it is far more efficient and powerful to write code like this:

dispatch_async(my_obj->queue, ^{
    bool done = false;
    // do_full_update() takes too long, therefore:
    while ( !my_obj->cancelled && !done ) {
        done = do_partial_update(my_obj);
    }
});

Oh, and to know if a queue has finished running all of the enqueued blocks, your code can simply execute an empty block with the synchronous API:

dispatch_sync(my_obj->queue, ^{});

As mentioned in the comments, a better way of knowing when your work is done is to use dispatch groups. Dispatch all your blocks to the group and then you can add a completion handler to the group. Once the work is complete, the completion block will run.

dispatch_group_t myGroup = dispatch_group_create();
dispatch_group_async(myGroup, my_obj->queue, ^{
    bool done = false;
    while ( !my_obj->cancelled && !done ) {
        done = do_partial_update(my_obj);
    }
});
dispatch_group_notify(myGroup, my_obj->queue, ^{
    NSLog(@"Work is done!");
    dispatch_release(myGroup);
});

Once all of your blocks have completed, the group will be empty and trigger the notification block. From there, you can update UI, etc.

Good luck and have fun!

like image 122
Anonymous Avatar answered Sep 27 '22 02:09

Anonymous


How to tell if is running

BOOL dispatch_queue_is_empty(dispatch_queue_t queue)
{
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        dispatch_group_leave(group);
    });

    int64_t maxWaitTime = 0.00000005 * NSEC_PER_SEC;
    BOOL isReady = dispatch_group_wait(group, maxWaitTime) == 0;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        dispatch_release(group);
    });

    return isReady;
}

To test in app

dispatch_queue_t queue = dispatch_queue_create("test", 0);

NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");

dispatch_async(queue, ^{
    for(int i = 0; i < 100; i++)
    {
        NSLog(@"... %i", i);
    }
});

NSLog(@"Is empty %@", dispatch_queue_is_empty(queue) ? @"YES" : @"NO");

Result

Is empty YES
Is empty NO
... 0
... 1
... 2
... 3
... 4
... 5
... 6
... 7
... 8
... 9

The default value for the variable maxWaitTime can be tweaked to wanted result.

like image 33
hfossli Avatar answered Sep 26 '22 02:09

hfossli


If you have a serial dispatch queue OR a concurrent dispatch queue, here is a code that can do the same thing.

BOOL __block queueIsEmpty = false;
dispatch_barrier_async (_dispatchQueue, ^{
    queueIsEmpty = true;
});

while (!queueIsEmpty) {
    int i = 0;  // NOOP instruction
}

// At this point your queue should be empty.
like image 43
Kris Subramanian Avatar answered Sep 27 '22 02:09

Kris Subramanian