如何在主队列同步地分派任务而不会出现死锁?

65

我需要在主队列上同步地分派一个block。我不知道当前是否正在主线程上运行。幼稚的解决方案如下:

dispatch_sync(dispatch_get_main_queue(), block);

但是,如果我当前正在主队列上运行的块中,这个调用会创建一个死锁。(同步调度等待该块完成,但该块甚至没有开始运行,因为我们正在等待当前的块完成。)

显然的下一步是检查当前队列:

if (dispatch_get_current_queue() == dispatch_get_main_queue()) {
    block();
} else {
    dispatch_sync(dispatch_get_main_queue(), block);
}

这个代码是能够工作的,但是它很丑陋。在我将其隐藏到一些自定义函数之前,难道没有更好的解决方案吗?我强调我无法异步地分派块 - 应用程序处于异步分派的块会被“太迟”执行的情况下。


我认为这是一个相当不错的解决方案。你所能做的就是将其作为预定义宏,这样你的代码看起来就不会那么丑陋了。 - Roman Temchenko
1
这更或多或少是“教科书”解决方案。 - Tobias Kräntzer
3个回答

69

我需要在我的Mac和iOS应用程序中经常使用类似这样的内容,因此我使用了以下辅助函数(最初在此答案中描述):

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

你需要调用的函数为

runOnMainQueueWithoutDeadlocking(^{
    //Do stuff
});

这基本上就是您上面描述的过程,我也与其他几位独立开发者交流过他们自己的类似实现。

我使用了[NSThread isMainThread]而不是检查dispatch_get_current_queue(),因为该函数的注意事项曾经警告过不要用于身份测试,并且在iOS 6中已被弃用


很好的回答,同时dispatch_get_current_queue()现在已经被弃用了。 - txulu
1
@Brad_Larson 是否存在真正的情况,即掌控其代码的开发人员不知道他们是否从主线程分支出来?我相信如果一个人知道他们的代码如何工作,则会知道主线程上发生了什么或将要发生什么。 - pnizzle
3
@pnizzle - 是的,有很多情况会发生这种情况。如果您有一个通用方法,其中执行的操作必须在主线程上运行,但是该方法从许多地方调用,一些在主线程上,一些不是,在这种情况下,这样的函数非常有用。我写这个函数是因为我自己在多个地方都需要用到它。 - Brad Larson
1
@pnizzle - 这不仅限于UIKit或AppKit,甚至不仅限于主线程。您可能希望保证在自己的串行队列上执行特定代码,以包装对共享资源的访问。例如,我经常为从非主串行队列访问OpenGL(ES)上下文而执行此操作,因为OpenGL本身无法处理它。我有一个串行IO库,只能在主线程上访问,但可能会在非主线程中调用。这不仅对UIKit和AppKit有帮助。 - Brad Larson
我该如何在Swift中实现这个? - Ankita Shah
显示剩余3条评论

2

对于在主队列或主线程上同步(这不是一回事),我使用以下代码:

import Foundation

private let mainQueueKey    = UnsafeMutablePointer<Void>.alloc(1)
private let mainQueueValue  = UnsafeMutablePointer<Void>.alloc(1)


public func dispatch_sync_on_main_queue(block: () -> Void)
{
    struct dispatchonce  { static var token : dispatch_once_t = 0  }
    dispatch_once(&dispatchonce.token,
    {
        dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueValue, nil)
    })

    if dispatch_get_specific(mainQueueKey) == mainQueueValue
    {
        block()
    }
    else
    {
        dispatch_sync(dispatch_get_main_queue(),block)
    }
}

extension NSThread
{
    public class func runBlockOnMainThread(block: () -> Void )
    {
        if NSThread.isMainThread()
        {
            block()
        }
        else
        {
            dispatch_sync(dispatch_get_main_queue(),block)
        }
    }

    public class func runBlockOnMainQueue(block: () -> Void)
    {
        dispatch_sync_on_main_queue(block)
    }
}

0

最近我在UI更新期间遇到了死锁问题。这引导我查看了Stack Overflow上的一个问题,从而实现了一个基于接受答案的runOnMainQueueWithoutDeadlocking类型的辅助函数。

然而,真正的问题是,在从块中更新UI时,我错误地使用了dispatch_sync而不是dispatch_async来获取用于UI更新的主队列。这很容易在代码完成时发生,并且在事后可能很难注意到。

因此,对于其他阅读此问题的人:如果不需要同步执行,则仅使用dispatch_**a**sync将避免您可能会间歇性遇到的死锁。


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