Как отправить в основную очередь синхронно без взаимоблокировки?
Мне нужно отправить блок в основной очереди, синхронно. Я не знаю, работает ли 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
, вы избежите тупиковой ситуации, с которой вы можете периодически проигрывать.