33

我只是在玩 GCD,我写了一个玩具 CoinFlipper 应用程序。

下面是抛硬币的方法:

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

    }
}

如你看到的; 我将翻转次数分解为大块,在后台翻转它们并更新主队列中的属性。窗口控制器正在观察这些属性,并使用运行结果更新 UI。

我查看了并发编程指南和 GCD 文档,虽然有一种方法可以暂停队列,但没有办法停止它们,并删除所有排队和未运行的对象。

我希望能够连接一个“停止”按钮来取消翻转一旦开始。NSOperationQueue我可以观察该operationCount属性以了解它是否正在运行,并删除cancelAllOperations排队的块。

我查看了并发编程指南和 GCD 文档,虽然有一种方法可以暂停队列,但没有办法停止它们,并删除所有排队和未运行的对象。

所以 :-

  1. 如何判断我添加到队列中的块是否仍在等待?
  2. 如何取消尚未运行的块?
  3. 我是 GCD 的新手,所以我做得对吗?
4

3 回答 3

49

这是使用 GCD 编程时的一个半常见问题。

简短的回答是 GCD 没有队列的取消 API。理由:

  1. 内存管理将变得更加复杂,因为给定的块可能负责释放()给定的内存分配。通过始终运行块,GCD 确保内存管理保持简单。
  2. 在不破坏状态的情况下停止正在运行的块实际上是不可能的。
  3. 大多数需要取消逻辑的代码已经在私有数据结构中跟踪该状态。

考虑到所有这些情况,编写这样的代码会更加高效和强大:

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

哦,要知道队列是否已经运行完所有入队的块,您的代码可以简单地使用同步API 执行一个空块:

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

如评论中所述,了解您的工作何时完成的更好方法是使用调度组。将所有块分派到组,然后您可以将完成处理程序添加到组。一旦工作完成,完成块将运行。

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

完成所有块后,该组将为空并触发通知块。从那里,您可以更新 UI 等。

祝好运并玩得开心点!

于 2009-10-11T16:15:47.543 回答
5

如何判断是否正在运行

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

在应用程序中测试

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");

结果

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

可以将变量的默认值maxWaitTime调整为想要的结果。

于 2013-10-14T17:31:27.030 回答
2

如果你有一个串行调度队列或一个并发调度队列,这里有一个可以做同样事情的代码。

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.
于 2014-07-17T22:02:22.510 回答