Grand Central Dispatch (GCD) 与 performSelector - 需要更好的解释

Posted

技术标签:

【中文标题】Grand Central Dispatch (GCD) 与 performSelector - 需要更好的解释【英文标题】:Grand Central Dispatch (GCD) vs. performSelector - need a better explanation 【发布时间】:2011-07-10 15:42:51 【问题描述】:

我在我的应用程序中同时使用了 GCD 和 performSelectorOnMainThread:waitUntilDone,并且倾向于认为它们可以互换——也就是说,performSelectorOnMainThread:waitUntilDone 是 GCD C 语法的 Obj-C 包装器。我一直认为这两个命令是等效的:

dispatch_sync(dispatch_get_main_queue(), ^ [self doit:YES]; );


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

我错了吗?也就是说, performSelector* 命令与 GCD 命令有区别吗?我已经阅读了很多关于它们的文档,但还没有看到明确的答案。

【问题讨论】:

withObject:YES 不起作用,至少应该给你一个警告。这可能是 GDC 的优势之一,您可以在其中向接收者发送任意参数。 对,我需要将它包装在一个 NSNumber 中。但是,忽略那部分,还有什么不同的吗?不过,好点子。 【参考方案1】:

正如 Jacob 所指出的,虽然它们可能看起来相同,但它们是不同的东西。事实上,如果您已经在主线程上运行,它们处理向主线程发送操作的方式存在显着差异。

我最近遇到了这个问题,我有一个常用的方法,有时是从主线程上的某个东西运行的,有时不是。为了保护某些 UI 更新,我一直使用-performSelectorOnMainThread: 来处理它们,没有任何问题。

当我切换到在主队列上使用dispatch_sync 时,只要在主队列上运行此方法,应用程序就会死锁。阅读dispatch_sync 上的文档,我们看到:

调用此函数并定位 当前队列导致死锁。

我们看到-performSelectorOnMainThread: 的位置

等待

一个布尔值,指定是否 当前线程阻塞,直到 指定的选择器在 主线程上的接收器。指定 YES 阻塞这个线程;否则, 指定 NO 让此方法返回 马上。

如果当前线程也是主线程 线程,并为此指定 YES 参数,消息被传递 并立即处理。

我仍然更喜欢 GCD 的优雅,它提供的更好的编译时检查,以及它在参数方面的更大灵活性等,所以我制作了这个小辅助函数来防止死锁:

void runOnMainQueueWithoutDeadlocking(void (^block)(void))

    if ([NSThread isMainThread])
    
        block();
    
    else
    
        dispatch_sync(dispatch_get_main_queue(), block);
    

更新:为了回应 Dave Dribin 指出 caveats section ondispatch_get_current_queue(),我已更改为在上述代码中使用 [NSThread isMainThread]

然后我用

runOnMainQueueWithoutDeadlocking(^
    //Do stuff
);

在主线程上执行我需要保护的操作,而不用担心原始方法在哪个线程上执行。

【讨论】:

@Joe - @Joe - dispatch_sync() 如果主线程上的某些东西在等待来自后台线程的值时被阻塞(通过dispatch_sync() 或其他方式),则主线程仍可能导致应用程序死锁)。这是非常不可能的,但仍有可能。这是两个线程等待另一个线程做某事的标准线程问题,所以什么都做不了。 迂腐溢出:您可以使用dispatch_block_t 作为参数类型,而不是丑陋的void (^block)(void) @Adam - 自从我写下该部分后,该部分似乎已更新。它曾经告诉你,不能保证身份检查对dispatch_get_current_queue() 有效。 这已被添加到警告部分:It is equally unsafe for code to assume that synchronous execution onto a queue is safe from deadlock if that queue is not the one returned by dispatch_get_current_queue(). @BradLarson 使用dispatch_async(dispatch_get_main_queue(), block) 怎么样?有什么问题吗?【参考方案2】:

performSelectorOnMainThread:使用 GCD 向主线程上的对象发送消息。

documentation 说明该方法的实现方式如下:

- (void) performSelectorOnMainThread:(SEL) selector withObject:(id) obj waitUntilDone:(BOOL) wait 
  [[NSRunLoop mainRunLoop] performSelector:selector target:self withObject:obj order:1 modes: NSRunLoopCommonModes];

performSelector:target:withObject:order:modes: 上,文档指出:

此方法设置一个计时器,在下一次运行循环迭代开始时在当前线程的运行循环上执行 aSelector 消息。定时器被配置为在模式参数指定的模式下运行。当计时器触发时,线程尝试将消息从运行循环中取出并执行选择器。如果运行循环正在运行并且处于指定模式之一,则成功;否则,计时器会一直等待,直到运行循环处于其中一种模式。

【讨论】:

【参考方案3】:

GCD 的方式被认为更高效且更易于处理,并且仅适用于 ios4 及更高版本,而新旧 iOS 均支持 performSelector。

【讨论】:

以上是关于Grand Central Dispatch (GCD) 与 performSelector - 需要更好的解释的主要内容,如果未能解决你的问题,请参考以下文章

暂停和恢复 Grand Central Dispatch 线程

swift Grand Central Dispatch(GCD)发送信号量示例

NSOperation 与 Grand Central Dispatch

使用 Grand Central Dispatch (GCD) 创建恰好 N 个线程

核心数据和线程/ Grand Central Dispatch

Grand Central Dispatch 用于复杂流程?