it-swarm.com.ru

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

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

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);
}

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

61
zoul

Мне нужно довольно часто использовать что-то подобное в моих приложениях 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(), потому что раздел caveats для этой функции однажды предупреждал против использования этого для проверки идентичности и вызов был исключен в iOS 6 .

68
Brad Larson

Для синхронизации в основной очереди или в основном потоке (это не одно и то же) я использую:

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)
    }
}
2
JollyJinx

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

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

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

0
pkamb