Почему 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 все еще в основном потоке.

Плохая идея вообще блокировать основной поток. Правильным решением является рефакторинг любого метода этого кода, чтобы использовать его собственный обработчик завершения вместо попытки использовать обычное возвращаемое значение.

Другие вопросы по тегам