Почему DispatchSemaphore.wait() блокирует этот обработчик завершения?
Итак, я играл с NetworkExtension, чтобы создать игрушечную реализацию VPN, и я столкнулся с проблемой с обработчиками завершения / асинхронно работающим кодом. Я расскажу вам о своих мыслях / истечениях срока действия и буду признателен за любые указания на области, в которых я ошибаюсь, и способы решения этой проблемы!
Вот самый маленький воспроизводимый фрагмент кода (очевидно, вам нужно import NetworkExtension
):
let semaphore = DispatchSemaphore(value: 0)
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
semaphore.signal()
}
print("1 before")
semaphore.wait()
print("3 after")
С моим пониманием семафоров и асинхронного кода я ожидал, что распечатки будут происходить в следующем порядке:
1 before
2 during
3 after
Однако программа зависает на "1 перед". Если я удалюsemaphore.wait()
В строке, распечатка выполняется, как и ожидалось, в порядке: 1, 3, 2 (поскольку закрытие выполняется позже).
Итак, после небольшого поиска в отладчике, похоже, что цикл перехвата семафоров блокирует выполнение. Это побудило меня немного читать в очередях, и я обнаружил, что изменение его на следующие работает:
// ... as before
DispatchQueue.global().async {
semaphore.wait()
print("3 after")
}
В этом есть смысл, поскольку блокировка .wait()
call теперь вызывается асинхронно в отдельном потоке. Однако это решение не желательно для меня, так как в моей реальной реализации я фактически фиксирую результаты закрытия и возвращаю их позже, примерно так:
let semaphore = DispatchSemaphore(value: 0)
var results: [NETunnelProviderManager]? = nil
NETunnelProviderManager.loadAllFromPreferences { managers, error in
print("2 during")
results = managers
semaphore.signal()
}
print("1 before")
// DispatchQueue.global().async {
semaphore.wait()
print("3 after")
// }
return results
Очевидно, я не могу вернуть данные из async
закрытие, и перемещение возврата из него сделало бы его несуществующим. Кроме того, добавление еще одного семафора, чтобы сделать вещи синхронными, демонстрирует ту же проблему, что и раньше, просто перемещение проблемы по цепочке.
В итоге решил попробовать поставить .loadAllFromPreferences()
обработчик вызова и завершения в async
закрытие и оставьте все остальное, как в исходном фрагменте кода:
// ...
DispatchQueue.global().async {
NETunnelProviderManager.loadAllFromPreferences { loadedManagers, error in
print("2 during")
semaphore.signal()
}
}
// ...
Однако это не работает, и .wait()
звонок ни разу не прошел - как и раньше. Я предполагаю, что каким-то образом семпахор все еще блокирует поток и не позволяет чему-либо выполняться, что означает, что все, что в системе управляет очередью, не запускает асинхронный блок? Однако я хватаюсь за соломинку и боюсь, что мой первоначальный вывод, возможно, был неверным.
Здесь я начинаю выходить из своей глубины, поэтому я хотел бы знать, что на самом деле происходит, и какое разрешение вы бы порекомендовали для получения результатов .loadAllFromPreferences()
синхронно?
Спасибо!
1 ответ
Из документации для NETunnelProviderManager loadAllFromPreferences
:
Этот блок будет выполнен в основном потоке вызывающей стороны после завершения операции загрузки.
Итак, мы знаем, что обработчик завершения находится в основном потоке.
Мы также знаем, что призыв к DispatchSemaphore wait
заблокирует любой поток, в котором он работает. Учитывая это свидетельство, вы должны вызывать весь этот код из основного потока. С момента вашего звонкаwait
блокирует основной поток, обработчик завершения никогда не может быть вызван, потому что основной поток заблокирован.
Это становится ясно из вашей попытки позвонить wait
в некоторой глобальной фоновой очереди. Это позволяет вызывать блок завершения, потому что вы используетеwait
больше не блокирует основной поток.
И твоя попытка позвонить loadAllFromPreferences
из глобальной фоновой очереди ничего не меняет, потому что его блок завершения все еще вызывается в основном потоке, а ваш вызов wait
все еще в основном потоке.
Плохая идея вообще блокировать основной поток. Правильным решением является рефакторинг любого метода этого кода, чтобы использовать его собственный обработчик завершения вместо попытки использовать обычное возвращаемое значение.