* Почему * не является повторным участником run_until_complete. Как постепенно портировать на асин c без потоков? - PullRequest
2 голосов
/ 14 января 2020

Допустим, мне поручено перенести проект Flask на asyn c Python веб-сервер. Я ищу шаблоны, чтобы минимизировать объем работы здесь. Мне кажется, более или менее, невозможно постепенно портировать syn c веб-серверы в asyn c веб-серверы. Это заставляет меня думать, что я неправильно понял asyn c.

Предположим, я хочу использовать библиотеку asyncio sql и использовать ее на веб-сервере asyncio, возможно, нам придется изменить следующий стек методы asyn c:

if __name__=='__main__':
    asyncio.get_event_loop().run_until_complete(main)

> async def main()
  > async def subfunc()
    > async def subsubfunc()
      > async def decorator1()
        > async def decorator2()
          > async def webapi()
            > async def decorator3()
              > async def decorator4()
                > async def servicemethod()
                  > async def servicemethod_impl()
                                ....
                    > async def decorator5()
                      > async def decorator6()
                        > async def repositorylayer()
                          > async def sqllibrary()
                            > async def sqllibrary2()
                              > async def asyncio_socket.read()

^^, потому что мы хотим ждать asyncio_socket.read (), тогда каждая функция в стеке должна быть изменена, имеет объявление функции async def, а также await на его зависимость. Это имеет некоторые серьезные последствия для рефакторинга:

  • нам нужно изменить функцию до n, чтобы получить преимущество одного asyncio_socket.read (), большинство из которых мало заботятся о том, является ли чтение сокета syn c или asyn c. То есть мы ДОЛЖНЫ объявить каждую зависимую функцию асинхронной c и ожидать результата зависимости (!)
  • . Любая функция, которая раньше зависела от какой-либо функции в этом стеке (но не в этом стеке), также должна измениться на быть асин c. (!) Тесты модулей событий, которые могут быть нам не интересны при переходе на asyn c сегодня, должны быть изменены:
result = oldtest()
assert result==expected
result = asyncio.get_event_loop().run_until_complete(oldtest())
assert result==expected

Как правило, любая функция syn c, которая вызывает Функция asyn c нуждается в рефакторинге asyn c -await - то есть asyn c - это сложная функция. Любой код, который вызывает asyn c, должен быть заражен asyn c, независимо от того, заботится ли он об asyn c или нет.

Поскольку это означает глобальный рефакторинг, постепенный прирост не представляется возможным перенесите веб-сервис с syn c -land на asyn c -land в любом проекте, кроме самых маленьких. Я видел решения, которые перемещают выполнение в потоки на барьере sync / asyn c. Тем не менее, кажется, что это: - создает проблемы безопасности потоков - устраняет преимущества asyn c должны быть коммуникация и переключение контекста - уменьшает пропускную способность выполнения из-за GIL.

Однако, в принципе, это должно быть возможным для вызова асинхронных c функций из syn c function:

def syncfunc2():
   result = asyncio.get_event_loop().run_until_complete(asyncfunc1())
   return result

async def asyncfunc3():
   result = await asyncfunc2()
   return result

def syncfunc4():
   result = asyncio.get_event_loop().run_until_complete(asyncfunc3())
   return result

Однако по непонятной причине Python не позволяет этого и завершается с:

RuntimeError: This event loop is already running

Я думаю, что - это , позволяющее безопасно реализовать повторяющиеся циклы событий. Мы используем это для потоковых исполнителей, когда у нас закончились потоки - вызывающая функция run_until_complete может управлять выполнением события l oop до тех пор, пока оно не вернется, после чего выполнение возвращается исходному исполнителю (что предотвращает тупики исполнителей, но ожидающих исполнения). Это особенно просто в Python, потому что GIL позволяет нам тривиально гарантировать, что event_l oop либо:

  • не управляется другой функцией,
  • ожидает текущая функция для вызова await

и поэтому безопасно извлечь задачу из очереди и выполнить ее. Поскольку Python жалуется, если вы повторно вводите run_until_complete, это запрещает это, а также запрещает добавочное введение asyn c.

Итак:

  • почему не запускается run_until_complete?
  • возможно ли постепенно вводить асин c в большом кодовые базы без обращения к дополнительным потокам (и соответствующая потеря выгоды asyn c).
  • - это тот случай, когда asyn c эффективно раздвоил кодовую базу python в тех библиотеках, которые используют asyn c, а те что нет?

Похожие:

1 Ответ

2 голосов
/ 15 января 2020

Что вы хотите сделать - это использовать gevent . Это позволит вам обслуживать несколько ответов одновременно без потоков или значительных модификаций синхронного кода.

Если вы хотите использовать подход на основе asyncio, вам придется иметь дело с тем фактом, что каждая функция который имеет дело с сетевым вводом / выводом (как и любой контроллер или взаимодействие с БД) должно быть переписано , чтобы оно было async. Это сделано намеренно, чтобы помочь бороться с проблемами, вызванными параллелизмом. Это единственный способ всегда точно знать места, где функция может внезапно дать результат.

...