Как правильно заблокировать асинхронный код?

У меня есть тонны кода, написанного следующим образом:

public string SomeSyncOperation(int someArg)
{
   // sync code 
   SomeAsyncOperation(someArg, someOtherArg).ConfigureAwait(false).GetAwaiter().GetResult()
   // sync code
};

Здесь у нас есть некоторый код синхронизации, который должен обращаться к async api, поэтому он блокируется, пока результаты не будут готовы. Мы не можем метод изменить подпись и добавить async Вот. Итак, мы все равно ждем синхронно, поэтому нам нужно ConfigureAwait(false) Вот? Я почти уверен, что мы этого не делаем, но я немного боюсь удалить его, потому что он, вероятно, покрывает некоторые случаи использования (или почему я вижу это практически везде? Это просто культ груза?), И удаление этого вызова может привести к некоторым небезопасным результатам.

Так имеет ли это смысл вообще?

1 ответ

Решение

Как правильно заблокировать асинхронный код?

Вы не правильно блокируете асинхронный код. Блокировка не так. Спрашивать, как правильно поступить неправильно, - это не стартер.

Блокировка асинхронного кода неправильная из-за следующего сценария:

  • У меня есть объект в руке, представляющий асинхронную операцию.
  • Асинхронная операция сама асинхронно ожидает завершения второй асинхронной операции.
  • Вторая асинхронная операция будет запланирована для этого потока, когда цикл обработки сообщений выполнит код, связанный с сообщением, которое в данный момент находится в очереди сообщений этого потока.

И теперь вы можете выяснить, что происходит ужасно неправильно, когда вы пытаетесь получить результат синхронно с первой асинхронной операцией. Он блокируется до тех пор, пока его дочерняя асинхронная операция не будет завершена, что никогда не произойдет, потому что теперь мы заблокировали поток, который будет обслуживать запрос в будущем!

Ваш выбор:

  1. Сделайте весь свой стек вызовов правильно асинхронным и await результат.
  2. Не используйте этот API. Напишите эквивалентный синхронный API, который, как вы знаете, не блокируется с нуля, и вызывайте его правильно.
  3. Напишите неверную программу, которая иногда неожиданно блокируется.

Есть два способа написать правильную программу; написание синхронной оболочки над асинхронной функцией опасно и неправильно.

Теперь вы можете спросить, не 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 файлах, и мы полностью преобразуем интерфейсы синхронизации в смешанные.

Тогда займись! Это звучит довольно легко.

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