我在哪个 GCD 队列上运行,无论是不是是主队列?

Posted

技术标签:

【中文标题】我在哪个 GCD 队列上运行,无论是不是是主队列?【英文标题】:What GCD queue, main or not, am I running on?我在哪个 GCD 队列上运行,无论是否是主队列? 【发布时间】:2011-02-09 08:25:35 【问题描述】:

我正在尝试编写一些线程安全的方法,所以我正在使用:

...
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_sync(main,^
  [self doSomethingInTheForeground];
);
...

但是如果我在不必要的主线程上,我可以跳过所有那些调度调用,所以我想知道我目前在哪个线程上。我怎么知道?

或者,也许这样做并没有什么不同(在性能上)?

这样比较可以吗?

if (dispatch_get_main_queue() == dispatch_get_current_queue())...

【问题讨论】:

使用提到的一种技术不仅可以减少开销,而且如果您对正在运行的同一队列执行 dispatch_sync,您将死锁。 dispatch_async 没问题。 如果您需要更通用的解决方案来防止 libdispatch 中的死锁,请查看这些帮助程序。他们覆盖 main_queue,只是要小心。 gist.github.com/1205760 这个问题很好,但是示例代码显示了死锁模式。也许修改一下比较好。 【参考方案1】:

更新答案

Apple 文档已更改,现在说“当从提交块的上下文之外调用时,如果调用是从主线程执行的,则此函数返回主队列。如果调用是从任何其他线程进行的,此函数返回默认的并发队列。”所以检查dispatch_get_main_queue() == dispatch_get_current_queue() 应该可以。

原答案

使用dispatch_get_main_queue() == dispatch_get_current_queue() 将不起作用。 dispatch_get_current_queue 的文档说“当在提交块的上下文之外调用时,此函数返回默认并发队列”。默认并发队列不是主队列。

[NSThread isMainThread] 应该可以满足您的需求。请注意,[NSThread isMainThread] 可以适用于除主队列以外的队列,例如,当从主线程调用 dispatch_sync 时。

【讨论】:

实际上,它说“当从提交块的上下文之外调用时,此函数如果调用是从主线程执行的,则返回主队列。如果调用是从任何其他线程进行的,此函数返回默认的并发队列。” 非常感谢您的更新。我认为这是我们都期望它的工作方式:) 看起来dispatch_get_current_queue() 现在已被弃用,有什么替代方案? 你应该使用 dispatch_get_specific。请参阅***.com/a/15725847/412916 以及 cmets 中 Apple 论坛的链接。【参考方案2】:

随着dispatch_get_current_queue()的折旧,相当于(dispatch_get_main_queue() == dispatch_get_current_queue())

现在是通过队列标签比较是:

(dispatch_queue_get_label(dispatch_get_main_queue()) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL))

【讨论】:

请注意 dispatch_queue_get_label() 返回一个字符 *。检查两个调用返回的指针值是否足够? strcmp() 调用是多余的吗?【参考方案3】:

更新答案:

dispatch_get_current_queue() 现已弃用。

原答案:

在 OS-X 10.8 上,在头文件 (queue.h) 中,在 dispatch_get_current_queue() 函数上方的注释中是这样写的:

当在主线程调用 dispatch_get_current_queue() 时,它可能 或者可能不会返回与 dispatch_get_main_queue() 相同的值。 比较两者不是测试代码是否正在执行的有效方法 在主线程上。

我发现是因为

assert(dispatch_get_current_queue() == dispatch_get_main_queue());

在我在 ios 上运行良好但在 OS-X 上失败的代码中。

【讨论】:

【参考方案4】:

iOS 的新功能:10.0+ 和 macOS:10.12+(tvOS:10.0+,watchOS:3.0+)。

斯威夫特 4+:

检查是否在主队列上运行:

if (RunLoop.current == RunLoop.main) 
    print("On main queue")

   

断言是否在主队列上运行:

dispatchPrecondition(condition: .onQueue(.main))
// Code running on main queue
 

断言如果在主队列上运行:

dispatchPrecondition(condition: .notOnQueue(.main))
// Code NOT running on main queue

ObjC:检查是否在主队列上运行:

if ([[NSRunLoop currentRunLoop] isEqual: [NSRunLoop mainRunLoop]]) 
    NSLog(@"Running on main");
 else 
    NSLog(@"Not running on main");

   

断言是否在主队列上运行:

dispatch_assert_queue(dispatch_get_main_queue());
   

断言如果在主队列上运行:

dispatch_assert_queue_not(dispatch_get_main_queue());

注意:mainThread != mainQueue 主队列总是在主队列上运行 线程,但一个队列可能正在主线程上运行而没有 主队列。所以,不要混合使用线程和队列测试!

【讨论】:

【参考方案5】:

如果你在 Objective-C 中,并且希望在主线程上同步发生一些事情,使用它会不会更简单

[self performSelectorOnMainThread: @selector(doSomethingInTheForeground) 
                       withObject: nil 
                    waitUntilDone: YES];

这样做的好处是,如果你已经在主线程上,没关系,消息以正常方式发送。

【讨论】:

【参考方案6】:

使用dispatch_get_current_queue() 是不安全的,除非你没有调试。写得很清楚in the dispatch_queue man page:

注意事项

代码无法对dispatch_get_current_queue() 返回的队列做出任何假设。返回的队列可能有任意的策略,这些策略可能会让试图调度队列工作的代码感到惊讶。策略列表包括但不限于队列宽度(即串行与并发)、调度优先级、安全凭证或文件系统配置。因此,dispatch_get_current_queue() 只能用于身份测试或调试。

更好的事情(可能更复杂)是同步后台线程:

dispatch_sync(backgroundqueue,^
  [self doSomethingInTheBackground];
);

也许我完全错了,但这就是我的建议。

【讨论】:

明确声明“用于身份测试”没问题。这就是最初的问题所暗示的(尽管这不会像我在回答中提到的那样起作用)。【参考方案7】:

其实可以用的。

文档说

当从提交块的上下文之外调用时,这个 如果调用是从主队列执行的,函数返回主队列 线程。如果调用是从任何其他线程进行的,则此函数 返回默认的并发队列。

【讨论】:

【参考方案8】:

请注意,主队列与主线程不同。当在主线程的队列上使用 dispatch_sync() 时,非主队列很容易在主线程上运行。更罕见的是,在使用 dispatch_main() 代替 NSRunLoops 的命令行工具中(即 Cocoa/iOS 应用程序除外),主队列可能在主线程以外的其他地方执行。

如果您正在处理需要主队列而不仅仅是主线程的代码(例如,期望从 queue_get_specific 在主队列上设置的值的代码,据传闻 VectorKit/MapKit 会这样做),最好显式检查主队列而不是主线程。

一个显式检查主队列的选项:

BOOL MyIsMainQueue(void)

    static char MAIN_IND_KEY;
    static char MAIN_IND_VAL;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
        dispatch_queue_set_specific(dispatch_get_main_queue(), &MAIN_IND_KEY, &MAIN_IND_VAL, NULL);
    );

    return dispatch_get_specific(&MAIN_IND_KEY) == &MAIN_IND_VAL;

使用 DISPATCH_CURRENT_QUEUE_LABEL 的答案也应该有效,并且可能会更好,尽管在 MacOS 10.9 和 iOS7 之前定义 DISPATCH_CURRENT_QUEUE_LABEL 时可能无效(即崩溃)。

【讨论】:

【参考方案9】:

dispatch_get_current_queue 自 iOS 6.0 起已弃用。看起来[NSThread isMainThread] 是唯一的出路。

【讨论】:

我在苹果和第三方的许多教程和文档中看到了 dispatch_get_main_queue 的用法。它似乎是一个非常广泛使用的功能。我因此挑战它一般被弃用。能给个参考吗?有直接替代策略吗? 你是对的,我剪切/粘贴函数的名称是错误的。不推荐使用的是 dispatch_get_current_queue 。很抱歉造成混乱。然而,结论仍然成立。 [NSThread isMainThread] 是唯一的方法(尽管它并不总是意味着你在主队列中)。 啊哈,对我来说也是新的,但是我同意。 :)

以上是关于我在哪个 GCD 队列上运行,无论是不是是主队列?的主要内容,如果未能解决你的问题,请参考以下文章

dispatch

如何在不使用 dispatch_get_current_queue() 的情况下验证我是不是在给定的 GCD 队列上运行?

GCD 串行队列调度异步和同步

GCD 全局队列任务在主线程上运行

GCD 异步串行队列 - 可以限制队列大小?

GCD编程-串行队列与并发队列