调度队列:如何判断它们是否正在运行以及如何停止它们

发布于 2024-08-07 06:51:52 字数 1779 浏览 3 评论 0原文

我只是在玩 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 的新手,所以我这样做对吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

原谅我要高飞 2024-08-14 06:51:52

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

简而言之,GCD 没有用于队列的取消 API。基本原理:

  1. 内存管理将变得更加复杂,因为给定的块可能负责 free() 给定的内存分配。通过始终运行该块,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 等。

祝您好运,玩得开心!

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!

三生路 2024-08-14 06:51:52

如何判断是否正在运行

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 的默认值调整为想要的结果。

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.

梦里寻她 2024-08-14 06:51:52

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

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.

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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文