Как отправить в основную очередь синхронно без взаимоблокировки?

Мне нужно отправить блок в основной очереди, синхронно. Я не знаю, работает ли Im в основном потоке или нет. Наивное решение выглядит так:

dispatch_sync(dispatch_get_main_queue(), block);

Но если Im в настоящее время внутри блока, запущенного в главной очереди, этот вызов создает тупик. (Синхронная отправка ждет завершения блока, но блок даже не запускается, так как мы ждем завершения текущего.)

Очевидным следующим шагом будет проверка текущей очереди:

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

Это работает, но его уродливо. Прежде чем я по крайней мере скрою его за какой-то пользовательской функцией, разве нет лучшего решения этой проблемы? Я подчеркиваю, что я не могу позволить рассылать блок асинхронно - приложение находится в ситуации, когда асинхронно отправленный блок будет выполнен "слишком поздно".

Ответ 1

Мне нужно использовать что-то подобное довольно регулярно в моих приложениях 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.

Ответ 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)
    }
}

Ответ 3

Недавно я начал испытывать тупик во время обновлений пользовательского интерфейса. Это привело меня к этому вопросу о переполнении стека, что привело к внедрению вспомогательной функции runOnMainQueueWithoutDeadlocking -type, основанной на принятом ответе.

Реальная проблема заключается в том, что при обновлении пользовательского интерфейса из блока я ошибочно использовал dispatch_sync, а не dispatch_async, чтобы получить основную очередь для обновлений пользовательского интерфейса. Легко сделать с завершением кода, и, возможно, его трудно заметить после этого.

Итак, для других, читающих этот вопрос: , если синхронное выполнение не требуется, просто используя dispatch_**a**sync, вы избежите тупиковой ситуации, с которой вы можете периодически проигрывать.