在主线程中执行任务的GCD

285

我有一个回调函数,可能来自任何线程。当我收到这个回调时,我想在主线程上执行某个特定任务。

我需要检查是否已经在主线程上,还是在调用下面的代码之前没有进行此检查会有任何惩罚吗?

dispatch_async(dispatch_get_main_queue(), ^{
   // do work here
});

165
五年后我仍然记不清GCD块的语法,每次都会来到这里。 - SpaceTrucker
11
@SpaceTrucker - 这也是我来到这个页面的原因 :D - Matthew Cawley
14
九年过去了,我仍然会来复制这个页面的语法。 - Osa
9
代码复制在问题中而不是答案中!这就是为什么提问如此重要的原因,尤其是在IT领域。 - GeneCode
6
将近十年后... - igoMobile
2
9年11个月,现在我用SWIFT编写代码,但不时需要更新我的Obj C页面。 - PDG
4个回答

164
不需要检查当前是否在主线程上。通过将块调度到主队列,您只是在主线程上按顺序安排块的执行,这发生在相应的运行循环运行时。
如果您已经在主线程上,则行为相同:块被安排,并在主线程的运行循环运行时执行。

3
问题是是否存在“不执行此检查的惩罚”...我认为在不必要使用异步调度时,会有性能惩罚,或者这很微不足道吗? - Dan Rosenstark
1
@Yar,我认为在大多数情况下,没有明显的性能影响:GCD是一个轻量级的库。也就是说,我理解问题是:“在下面的代码中,我需要检查是否在主线程上吗?” - user557219
8
但是,您需要检查是否使用dispatch_sync。否则会发生死锁。 - Victor Engel
如果您在主队列上并将async调度回主队列,则它将被运行,但这可能会破坏您的操作的预期时间。例如,在viewDidLoad()中的UI代码直到显示视图后才运行。 - pkamb

112

对于您上述描述的异步调度情况,您不需要检查是否在主线程上运行。如Bavarious所示,该操作将被排队在主线程上运行。

但是,如果您尝试使用dispatch_sync()执行上述操作,而且您的回调函数位于主线程上,则应用程序将在那一点上发生死锁。我在我的答案中描述了这一点,因为当我将一些代码从-performSelectorOnMainThread:移动时,这种行为让我很惊讶。正如我在那里提到的,我创建了一个辅助函数:

void runOnMainQueueWithoutDeadlocking(void (^block)(void))
{
    if ([NSThread isMainThread])
    {
        block();
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(), block);
    }
}

如果你所在的方法不在主线程上,则该代码将在主线程上同步运行一个代码块,并且如果在主线程上,则直接执行该代码块。您可以使用以下语法来使用此功能:

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

2
为什么不把它命名为“runOnMainQueueSync”之类的东西呢?它可能会死锁,但实际上并没有发生,这种情况我不想在我的代码中到处都有。一如既往地感谢您的帮助和支持。 - Dan Rosenstark
2
@Yar - 我只是给它起了这个名字,以便我清楚地知道为什么我不应该在主队列上同步触发块,而是使用这个辅助函数。这更像是对自己的提醒。 - Brad Larson
5
使用这个函数仍然可能会出现死锁。主队列同步 > 其他队列同步 > 主队列将会导致死锁。 - hfossli
3
@hfossli - 是的,它不能处理每种情况。在我的使用中,我总是从后台串行队列上的异步调度或主线程上的内联代码中调用。我想知道如果在您描述的情况下使用dispatch_set_specific()是否有帮助:https://dev59.com/J2cs5IYBdhLWcg3wfEHa#12806754。 - Brad Larson
1
[NSThread isMainThread] 经常返回 YES,不被认为是在 GCD 编程中检查此情况的安全方式。https://dev59.com/1GUq5IYBdhLWcg3wMNcK - Will Larche
@WillLarche - 在这种情况下,是安全的。以上的整个目的是保证在主线程上同步执行特定的块。如果此函数在主线程上运行,即使它不在主队列中(您指出的情况),-isMainThread将返回YES,并且该块将按照应有的方式内联运行。它保证了在主线程上执行,从而防止最直接的死锁情况(如上所述,并非所有情况,但是我在我的代码中遇到的情况)。 - Brad Larson

69

就像其他答案提到的那样,从主线程中使用dispatch_async是可以的。

但是,根据您的使用情况,可能会有一个您认为是缺点的副作用:由于该块被安排在队列上,直到控制返回到运行循环时才会执行,这将导致延迟块的执行。

例如,

NSLog(@"before dispatch async");
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"inside dispatch async block main thread from main thread");
});
NSLog(@"after dispatch async");

将打印出:

before dispatch async
after dispatch async
inside dispatch async block main thread from main thread

因此,如果您希望此代码块在外部 NSLog 之间执行,使用 dispatch_async 将无法实现您的目的。


需要强调的是,这是一个需要考虑的重要事项。 - Marc-Alexandre Bérubé
既然您想进行检查,这就意味着代码可能会或可能不会在主线程中运行。如果已经在主线程中,则按该顺序运行,否则无法保证。因此,在参考多线程编程时,应避免设计以特定顺序运行的代码。尝试使用异步回调方式。 - ooops

1

不需要检查是否在主线程。以下是Swift中的实现方法:

runThisInMainThread { () -> Void in
    runThisInMainThread { () -> Void in
        // No problem
    }
}

func runThisInMainThread(block: dispatch_block_t) {
    dispatch_async(dispatch_get_main_queue(), block)
}

这是我的代码库中的标准函数,包含在其中,请查看:https://github.com/goktugyil/EZSwiftExtensions


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接