Как правильно заблокировать асинхронный код?
У меня есть тонны кода, написанного следующим образом:
public string SomeSyncOperation(int someArg)
{
// sync code
SomeAsyncOperation(someArg, someOtherArg).ConfigureAwait(false).GetAwaiter().GetResult()
// sync code
};
Здесь у нас есть некоторый код синхронизации, который должен обращаться к async api, поэтому он блокируется, пока результаты не будут готовы. Мы не можем метод изменить подпись и добавить async
Вот. Итак, мы все равно ждем синхронно, поэтому нам нужно ConfigureAwait(false)
Вот? Я почти уверен, что мы этого не делаем, но я немного боюсь удалить его, потому что он, вероятно, покрывает некоторые случаи использования (или почему я вижу это практически везде? Это просто культ груза?), И удаление этого вызова может привести к некоторым небезопасным результатам.
Так имеет ли это смысл вообще?
1 ответ
Как правильно заблокировать асинхронный код?
Вы не правильно блокируете асинхронный код. Блокировка не так. Спрашивать, как правильно поступить неправильно, - это не стартер.
Блокировка асинхронного кода неправильная из-за следующего сценария:
- У меня есть объект в руке, представляющий асинхронную операцию.
- Асинхронная операция сама асинхронно ожидает завершения второй асинхронной операции.
- Вторая асинхронная операция будет запланирована для этого потока, когда цикл обработки сообщений выполнит код, связанный с сообщением, которое в данный момент находится в очереди сообщений этого потока.
И теперь вы можете выяснить, что происходит ужасно неправильно, когда вы пытаетесь получить результат синхронно с первой асинхронной операцией. Он блокируется до тех пор, пока его дочерняя асинхронная операция не будет завершена, что никогда не произойдет, потому что теперь мы заблокировали поток, который будет обслуживать запрос в будущем!
Ваш выбор:
- Сделайте весь свой стек вызовов правильно асинхронным и
await
результат. - Не используйте этот API. Напишите эквивалентный синхронный API, который, как вы знаете, не блокируется с нуля, и вызывайте его правильно.
- Напишите неверную программу, которая иногда неожиданно блокируется.
Есть два способа написать правильную программу; написание синхронной оболочки над асинхронной функцией опасно и неправильно.
Теперь вы можете спросить, не ConfigureAwait
решить проблему, убрав требование возобновить работу в текущем контексте? Это не точка возобновления, о которой мы беспокоимся. Если вы собираетесь положиться на ConfigureAwait
чтобы спасти вас от взаимоблокировки, тогда каждая асинхронная операция в стеке должна использовать ее, и мы не знаем, сделала ли это базовая асинхронная операция, которая вот-вот вызовет взаимоблокировку!
Если вышесказанное не совсем понятно для вас, прочитайте статью Стивена о том, почему это плохая практика и почему обычные обходные пути - просто опасные хаки.
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
и его обновленная статья, дающая больше хаков и обходных путей здесь:
https://msdn.microsoft.com/en-us/magazine/mt238404.aspx?f=255&MSPPError=-2147217396
Но опять же: правильная вещь - это перепроектировать вашу программу, чтобы принять асинхронность и использовать await
на протяжении. Не пытайтесь обойти это.
потому что этот метод имеет трассировку стека ~20 методов, некоторые из них реализуют некоторые интерфейсы. Чтобы изменить его на асинхронный, потребуются объявления об изменениях в ~50 файлах, и мы полностью преобразуем интерфейсы синхронизации в смешанные.
Тогда займись! Это звучит довольно легко.