# Розробка з 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 100 milliseconds 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 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 Методи циклу подій 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)

Network logging can block the event loop. It is recommended to use a separate thread for handling logs or use non-blocking IO. For example, see Робота з обробниками, які блокують.

## Виявляти ніколи не очікувані співпрограми

Коли функція співпрограми викликається, але не очікується (наприклад, `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