Розробка з asyncio¶
Асинхронне програмування відрізняється від класичного «послідовного» програмування.
На цій сторінці наведено типові помилки та пастки та пояснено, як їх уникнути.
Режим налагодження¶
За замовчуванням asyncio працює у робочому режимі. Щоб полегшити розробку, asyncio має режим налагодження.
Є кілька способів увімкнути асинхронний режим налагодження:
Встановлення змінної середовища
PYTHONASYNCIODEBUG
на1
.Використання режиму розробки Python.
Передача
debug=True
доasyncio.run()
.Виклик
loop.set_debug()
.
Окрім увімкнення режиму налагодження, враховуйте також:
setting the log level of the asyncio logger to
logging.DEBUG
, for example the following snippet of code can be run at startup of the application:logging.basicConfig(level=logging.DEBUG)
налаштування модуля
warnings
для відображення попередженьResourceWarning
. Один із способів зробити це — скористатися параметром командного рядка-W
default
.
Коли ввімкнено режим налагодження:
asyncio перевіряє співпрограми, які не були очікувані, і записує їх у журнал; це пом’якшує помилку «забутого очікування».
Багато небезпечних для потоків асинхронних API (таких як методи
loop.call_soon()
іloop.call_at()
) викликають виняток, якщо вони викликаються з неправильного потоку.Час виконання селектора введення-виведення реєструється, якщо виконання операції введення-виведення займає надто багато часу.
Callbacks taking longer than 100ms are logged. The
loop.slow_callback_duration
attribute can be used to set the minimum execution duration in seconds that is considered «slow».
Паралельність і багатопотоковість¶
Цикл подій виконується в потоці (зазвичай головному) і виконує всі зворотні виклики та завдання у своєму потоці. Поки Завдання виконується в циклі подій, жодні інші Завдання не можуть виконуватися в тому самому потоці. Коли Завдання виконує вираз очікування
, запущене Завдання призупиняється, а цикл подій виконує наступне Завдання.
Щоб запланувати callback з іншого потоку ОС, слід використовувати метод loop.call_soon_threadsafe()
. Приклад:
loop.call_soon_threadsafe(callback, *args)
Майже всі асинхронні об’єкти не є потокобезпечними, що зазвичай не є проблемою, якщо немає коду, який працює з ними поза Завданням або зворотним викликом. Якщо існує потреба в такому коді для виклику низькорівневого асинхронного API, слід використовувати метод loop.call_soon_threadsafe()
, наприклад:
loop.call_soon_threadsafe(fut.cancel)
Щоб запланувати об’єкт співпрограми з іншого потоку ОС, слід використати функцію run_coroutine_threadsafe()
. Він повертає concurrent.futures.Future
для доступу до результату:
async def coro_func():
return await asyncio.sleep(1, 42)
# Later in another OS thread:
future = asyncio.run_coroutine_threadsafe(coro_func(), loop)
# Wait for the result:
result = future.result()
To handle signals and to execute subprocesses, the event loop must be run in the main thread.
Метод loop.run_in_executor()
можна використовувати з concurrent.futures.ThreadPoolExecutor
для виконання коду блокування в іншому потоці ОС, не блокуючи потік ОС, у якому виконується цикл подій.
There is currently no way to schedule coroutines or callbacks directly
from a different process (such as one started with
multiprocessing
). The Event Loop Methods
section lists APIs that can read from pipes and watch file descriptors
without blocking the event loop. In addition, asyncio’s
Subprocess APIs provide a way to start a
process and communicate with it from the event loop. Lastly, the
aforementioned loop.run_in_executor()
method can also be used
with a concurrent.futures.ProcessPoolExecutor
to execute
code in a different process.
Запуск коду блокування¶
Код блокування (прив’язаний до процесора) не слід викликати безпосередньо. Наприклад, якщо функція виконує інтенсивне обчислення ЦП протягом 1 секунди, усі одночасні асинхронні завдання та операції введення-виведення будуть відкладені на 1 секунду.
Виконавець можна використовувати для запуску завдання в іншому потоці або навіть в іншому процесі, щоб уникнути блокування потоку ОС за допомогою циклу подій. Додаткову інформацію див. у методі loop.run_in_executor()
.
Лісозаготівля¶
asyncio використовує модуль logging
, і все журналювання виконується через "asyncio"
реєстратор.
The default log level is logging.INFO
, which can be easily
adjusted:
logging.getLogger("asyncio").setLevel(logging.WARNING)
Виявляти ніколи не очікувані співпрограми¶
Коли функція співпрограми викликається, але не очікується (наприклад, coro()
замість await coro()
) або співпрограма не запланована за допомогою asyncio.create_task()
, asyncio видасть RuntimeWarning
:
import asyncio
async def test():
print("never scheduled")
async def main():
test()
asyncio.run(main())
Вихід:
test.py:7: RuntimeWarning: coroutine 'test' was never awaited
test()
Вивід у режимі налагодження:
test.py:7: RuntimeWarning: coroutine 'test' was never awaited
Coroutine created at (most recent call last)
File "../t.py", line 9, in <module>
asyncio.run(main(), debug=True)
< .. >
File "../t.py", line 7, in main
test()
test()
Звичайним виправленням є або очікування співпрограми, або виклик функції asyncio.create_task()
:
async def main():
await test()
Виявлення ніколи не отриманих винятків¶
Якщо викликається Future.set_exception()
, але об’єкт Future ніколи не очікується, виняток ніколи не поширюватиметься на код користувача. У цьому випадку asyncio створюватиме повідомлення журналу, коли об’єкт Future збиратиме сміття.
Приклад необробленого винятку:
import asyncio
async def bug():
raise Exception("not consumed")
async def main():
asyncio.create_task(bug())
asyncio.run(main())
Вихід:
Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
exception=Exception('not consumed')>
Traceback (most recent call last):
File "test.py", line 4, in bug
raise Exception("not consumed")
Exception: not consumed
Увімкніть режим налагодження, щоб отримати відстеження місця створення завдання:
asyncio.run(main(), debug=True)
Вивід у режимі налагодження:
Task exception was never retrieved
future: <Task finished coro=<bug() done, defined at test.py:3>
exception=Exception('not consumed') created at asyncio/tasks.py:321>
source_traceback: Object created at (most recent call last):
File "../t.py", line 9, in <module>
asyncio.run(main(), debug=True)
< .. >
Traceback (most recent call last):
File "../t.py", line 4, in bug
raise Exception("not consumed")
Exception: not consumed