fastapi/docs/uk/docs/async.md

445 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Рівночасність і async / await { #concurrency-and-async-await }
Деталі щодо синтаксису `async def` для функцій операції шляху і деякі відомості про асинхронний код, рівночасність і паралелізм.
## Поспішаєте? { #in-a-hurry }
<abbr title="too long; didn't read - занадто довго; не читав"><strong>TL;DR:</strong></abbr>
Якщо ви використовуєте сторонні бібліотеки, які вимагають виклику з `await`, наприклад:
```Python
results = await some_library()
```
Тоді оголошуйте ваші функції операції шляху з `async def`, наприклад:
```Python hl_lines="2"
@app.get('/')
async def read_results():
results = await some_library()
return results
```
/// note | Примітка
Ви можете використовувати `await` лише всередині функцій, створених з `async def`.
///
---
Якщо ви використовуєте сторонню бібліотеку, яка взаємодіє з чимось (база даних, API, файлова система тощо) і не підтримує використання `await` (наразі це стосується більшості бібліотек баз даних), тоді оголошуйте ваші функції операції шляху як зазвичай, просто з `def`, наприклад:
```Python hl_lines="2"
@app.get('/')
def results():
results = some_library()
return results
```
---
Якщо ваш застосунок (якимось чином) не має комунікувати з чимось іншим і чекати на відповідь, використовуйте `async def`, навіть якщо вам не потрібно використовувати `await` всередині.
---
Якщо ви не певні, використовуйте звичайний `def`.
---
Примітка: ви можете змішувати `def` і `async def` у ваших функціях операції шляху скільки завгодно і визначати кожну з них найкращим для вас способом. FastAPI зробить з ними все правильно.
У будь-якому з наведених випадків FastAPI все одно працюватиме асинхронно і буде надзвичайно швидким.
Але слідуючи крокам вище, він зможе зробити деякі оптимізації продуктивності.
## Технічні деталі { #technical-details }
Сучасні версії Python мають підтримку «асинхронного коду» за допомогою так званих «співпрограм» з синтаксисом **`async` і `await`**.
Розгляньмо цю фразу по частинах у секціях нижче:
- Асинхронний код
- `async` і `await`
- Співпрограми
## Асинхронний код { #asynchronous-code }
Асинхронний код означає, що мова 💬 має спосіб сказати комп’ютеру/програмі 🤖, що в певний момент у коді він 🤖 має почекати, поки «щось інше» завершиться десь ще. Скажімо, це «щось інше» називається «slow-file» 📝.
Отже, в цей час комп’ютер може піти і зробити іншу роботу, доки «slow-file» 📝 завершується.
Далі комп’ютер/програма 🤖 повертатиметься щоразу, коли матиме можливість, бо знову чекає, або коли він 🤖 завершив усю роботу, яка була в нього на той момент. І він 🤖 перевірить, чи якась із задач, на які він чекав, уже завершилася, виконавши все, що потрібно.
Потім він 🤖 бере першу завершену задачу (скажімо, наш «slow-file» 📝) і продовжує робити те, що потрібно було зробити з нею.
Це «чекати на щось інше» зазвичай стосується операцій <abbr title="Input and Output - Ввід та Вивід">I/O</abbr>, які відносно «повільні» (порівняно зі швидкістю процесора та пам’яті з довільним доступом), наприклад, очікування:
- даних від клієнта, що надсилаються мережею
- даних, надісланих вашим застосунком, які клієнт має отримати мережею
- вмісту файла на диску, який система має прочитати і передати вашому застосунку
- вмісту, який ваш застосунок передав системі, щоб він був записаний на диск
- віддаленої операції API
- завершення операції бази даних
- повернення результатів запиту до бази даних
- тощо
Оскільки час виконання переважно витрачається на очікування операцій <abbr title="Input and Output - Ввід та Вивід">I/O</abbr>, їх називають операціями «I/O bound».
Це називається «асинхронним», тому що комп’ютеру/програмі не потрібно бути «синхронізованими» з повільною задачею, очікуючи точного моменту її завершення, нічого не роблячи, лише щоб отримати результат задачі та продовжити роботу.
Натомість, у «асинхронній» системі щойно завершена задача може трохи зачекати в черзі (кілька мікросекунд), доки комп’ютер/програма завершить те, що пішов робити, а потім повернеться, щоб забрати результати і продовжити роботу з ними.
Для «синхронного» (на противагу «асинхронному») часто також використовують термін «послідовний», бо комп’ютер/програма слідує всім крокам послідовно, перш ніж перемкнутися на іншу задачу, навіть якщо ці кроки включають очікування.
### Рівночасність і бургери { #concurrency-and-burgers }
Ідею **асинхронного** коду, описану вище, інколи також називають **«рівночасністю»**. Вона відрізняється від **«паралелізму»**.
І рівночасність, і паралелізм стосуються «різних речей, що відбуваються більш-менш одночасно».
Але деталі між рівночасністю і паралелізмом досить різні.
Щоб побачити різницю, уявімо таку історію про бургери:
### Рівночасні бургери { #concurrent-burgers }
Ви йдете зі своєю симпатією по фастфуд, стаєте в чергу, доки касир приймає замовлення у людей перед вами. 😍
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration">
Потім ваша черга, ви замовляєте 2 дуже вишукані бургери для вашої симпатії і для себе. 🍔🍔
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration">
Касир каже щось кухарю на кухні, щоб той знав, що треба приготувати ваші бургери (хоча зараз він готує бургери для попередніх клієнтів).
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration">
Ви платите. 💸
Касир дає вам номер вашої черги.
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration">
Поки ви чекаєте, ви з вашою симпатією обираєте столик, сідаєте і довго розмовляєте (адже ваші бургери дуже вишукані і потребують часу на приготування).
Сидячи за столиком із вашою симпатією, доки чекаєте бургери, ви можете витратити цей час, милуючись тим, яка ваша симпатія класна, мила і розумна ✨😍✨.
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration">
Чекаючи і спілкуючись із вашою симпатією, час від часу ви перевіряєте номер на табло біля прилавка, щоб побачити, чи вже ваша черга.
І от нарешті ваша черга. Ви підходите до прилавка, забираєте бургери і повертаєтеся до столика.
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration">
Ви з вашою симпатією їсте бургери і гарно проводите час. ✨
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration">
/// info | Інформація
Прекрасні ілюстрації від <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
///
---
Уявіть, що в цій історії ви - комп’ютер/програма 🤖.
Поки ви в черзі, ви просто бездіяльні 😴, чекаєте своєї черги, нічого «продуктивного» не роблячи. Але черга рухається швидко, бо касир лише приймає замовлення (а не готує їх), тож це нормально.
Коли ж ваша черга, ви виконуєте справді «продуктивну» роботу: переглядаєте меню, вирішуєте, що бажаєте, дізнаєтеся вибір вашої симпатії, платите, перевіряєте, що віддаєте правильну купюру чи картку, що з вас правильно списали кошти, що замовлення містить правильні позиції тощо.
Але потім, хоча у вас ще немає бургерів, ваша взаємодія з касиром «на паузі» ⏸, бо вам доводиться чекати 🕙, поки бургери будуть готові.
Втім, відійшовши від прилавка і сівши за столик із номерком, ви можете перемкнути 🔀 увагу на свою симпатію і «попрацювати» ⏯ 🤓 над цим. Тоді ви знову робите щось дуже «продуктивне» - фліртуєте зі своєю симпатією 😍.
Потім касир 💁 каже «Я закінчив робити бургери», виводячи ваш номер на табло прилавка, але ви не підстрибуєте миттєво, щойно номер змінюється на ваш. Ви знаєте, що ніхто не вкраде ваші бургери, адже у вас є номер вашої черги, а в інших - свій.
Тож ви чекаєте, поки ваша симпатія завершить історію (завершить поточну роботу ⏯/задачу 🤓), лагідно усміхаєтеся і кажете, що підете за бургерами ⏸.
Потім ви йдете до прилавка 🔀, до початкової задачі, яку тепер завершено ⏯, забираєте бургери, дякуєте і несете їх до столу. Це завершує той крок/задачу взаємодії з прилавком ⏹. Натомість з’являється нова задача «їсти бургери» 🔀 ⏯, але попередня «отримати бургери» завершена ⏹.
### Паралельні бургери { #parallel-burgers }
А тепер уявімо, що це не «рівночасні бургери», а «паралельні бургери».
Ви йдете зі своєю симпатією по паралельний фастфуд.
Ви стаєте в чергу, поки кілька (скажімо, 8) касирів, які водночас є кухарями, приймають замовлення у людей перед вами.
Кожен перед вами чекає, поки його бургери будуть готові, перш ніж відійти від прилавка, тому що кожен з 8 касирів одразу йде і готує бургер, перш ніж приймати наступне замовлення.
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration">
Нарешті ваша черга, ви замовляєте 2 дуже вишукані бургери для вашої симпатії і для себе.
Ви платите 💸.
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration">
Касир іде на кухню.
Ви чекаєте, стоячи перед прилавком 🕙, щоб ніхто інший не забрав ваші бургери раніше, ніж ви, адже номерків черги немає.
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration">
Оскільки ви з вашою симпатією зайняті тим, щоб ніхто не став перед вами і не забрав ваші бургери, щойно вони з’являться, ви не можете приділяти увагу своїй симпатії. 😞
Це «синхронна» робота, ви «синхронізовані» з касиром/кухарем 👨‍🍳. Вам доводиться чекати 🕙 і бути тут у точний момент, коли касир/кухар 👨‍🍳 завершить бургери і віддасть їх вам, інакше хтось інший може їх забрати.
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration">
Потім ваш касир/кухар 👨‍🍳 нарешті повертається з вашими бургерами після довгого очікування 🕙 перед прилавком.
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration">
Ви берете бургери і йдете до столика зі своєю симпатією.
Ви просто їх їсте - і все. ⏹
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration">
Багато розмов чи флірту не було, бо більшість часу пішла на очікування 🕙 перед прилавком. 😞
/// info | Інформація
Прекрасні ілюстрації від <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨
///
---
У цьому сценарії паралельних бургерів ви - комп’ютер/програма 🤖 з двома процесорами (ви і ваша симпатія), які обидва чекають 🕙 і приділяють увагу ⏯ «очікуванню біля прилавка» 🕙 тривалий час.
У закладу фастфуду 8 процесорів (касира/кухаря). У той час як у закладі з рівночасними бургерами могло бути лише 2 (один касир і один кухар).
Та все одно фінальний досвід не найкращий. 😞
---
Це була б паралельна історія про бургери. 🍔
Для більш «реального» прикладу уявіть банк.
До недавнього часу більшість банків мали кілька касирів 👨‍💼👨‍💼👨‍💼👨‍💼 і велику чергу 🕙🕙🕙🕙🕙🕙🕙🕙.
Усі касири робили всю роботу з одним клієнтом за іншим 👨‍💼⏯.
І вам доводилося 🕙 довго стояти в черзі, інакше ви втратите свою чергу.
Ви, напевно, не хотіли б брати свою симпатію 😍 із собою у справи до банку 🏦.
### Висновок про бургери { #burger-conclusion }
У цьому сценарії «фастфуд із вашою симпатією», оскільки є багато очікування 🕙, значно доцільніше мати рівночасну систему ⏸🔀⏯.
Так є у більшості вебзастосунків.
Багато-багато користувачів, але ваш сервер чекає 🕙 на їхнє не надто гарне з’єднання, щоб вони надіслали свої запити.
А потім знову чекає 🕙 на повернення відповідей.
Це «очікування» 🕙 вимірюється у мікросекундах, але все ж, у сумі - це багато очікування в підсумку.
Ось чому дуже логічно використовувати асинхронний ⏸🔀⏯ код для веб API.
Такий тип асинхронності зробив NodeJS популярним (хоча NodeJS не є паралельним), і це сила Go як мови програмування.
І такий самий рівень продуктивності ви отримуєте з **FastAPI**.
А оскільки можна мати паралелізм і асинхронність одночасно, ви отримуєте вищу продуктивність, ніж більшість протестованих фреймворків NodeJS, і на рівні з Go, який є компільованою мовою, ближчою до C <a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">(усе завдяки Starlette)</a>.
### Чи краща рівночасність за паралелізм? { #is-concurrency-better-than-parallelism }
Ні! Це не мораль історії.
Рівночасність відрізняється від паралелізму. І вона краща у конкретних сценаріях, що містять багато очікування. Через це зазвичай вона значно краща за паралелізм для розробки вебзастосунків. Але не для всього.
Щоб урівноважити це, уявімо коротку історію:
> Ви маєте прибрати великий брудний будинок.
*Так, це вся історія*.
---
Тут немає очікування 🕙 - просто багато роботи, яку треба зробити, у багатьох місцях будинку.
У вас могли б бути «черги» як у прикладі з бургерами: спочатку вітальня, потім кухня. Але оскільки ви ні на що не чекаєте 🕙, а просто прибираєте, «черги» нічого не змінять.
Завершення займе той самий час із «чергами» чи без (рівночасність), і ви виконаєте той самий обсяг роботи.
Але в цьому випадку, якби ви могли привести 8 колишніх касирів/кухарів/тепер прибиральників, і кожен з них (разом із вами) взяв би свою зону будинку для прибирання, ви могли б виконати всю роботу паралельно — з додатковою допомогою — і завершити значно швидше.
У цьому сценарії кожен з прибиральників (включно з вами) був би процесором, що виконує свою частину роботи.
І оскільки більшість часу виконання займає реальна робота (а не очікування), а роботу на комп’ютері виконує <abbr title="Central Processing Unit - Центральний процесор">CPU</abbr>, ці проблеми називають «CPU bound».
---
Поширені приклади «CPU bound» операцій - це речі, що потребують складної математичної обробки.
Наприклад:
- **Обробка аудіо** або **зображень**.
- **Комп’ютерний зір**: зображення складається з мільйонів пікселів, кожен піксель має 3 значення/кольори, обробка зазвичай потребує обчислення чогось над цими пікселями, усіма одночасно.
- **Машинне навчання**: зазвичай потребує великої кількості множень «матриць» і «векторів». Уявіть величезну таблицю з числами і множення всіх їх разом одночасно.
- **Глибоке навчання**: це підгалузь машинного навчання, тож те саме застосовується. Просто тут не одна таблиця чисел для множення, а величезний їх набір, і в багатьох випадках ви використовуєте спеціальний процесор для побудови та/або використання цих моделей.
### Рівночасність + паралелізм: веб + машинне навчання { #concurrency-parallelism-web-machine-learning }
З **FastAPI** ви можете скористатися рівночасністю, що дуже поширена у веброзробці (та ж головна принада NodeJS).
Але ви також можете використати переваги паралелізму і багатопроцесорності (наявність кількох процесів, що працюють паралельно) для навантажень «CPU bound», як у системах машинного навчання.
Це, плюс простий факт, що Python є основною мовою для **Data Science**, машинного навчання і особливо глибокого навчання, робить FastAPI дуже вдалим вибором для веб API та застосунків Data Science / машинного навчання (серед багатьох інших).
Щоб побачити, як досягти цього паралелізму у продакшні, див. розділ про [Розгортання](deployment/index.md){.internal-link target=_blank}.
## `async` і `await` { #async-and-await }
Сучасні версії Python мають дуже інтуїтивний спосіб визначення асинхронного коду. Це робить його схожим на звичайний «послідовний» код і виконує «очікування» за вас у відповідні моменти.
Коли є операція, яка вимагатиме очікування перед поверненням результатів і має підтримку цих нових можливостей Python, ви можете написати її так:
```Python
burgers = await get_burgers(2)
```
Ключ тут - `await`. Він каже Python, що потрібно почекати ⏸, поки `get_burgers(2)` завершить свою роботу 🕙, перш ніж зберегти результати в `burgers`. Завдяки цьому Python знатиме, що може піти і зробити щось інше 🔀 ⏯ тим часом (наприклад, прийняти інший запит).
Щоб `await` працював, він має бути всередині функції, що підтримує цю асинхронність. Для цього просто оголосіть її як `async def`:
```Python hl_lines="1"
async def get_burgers(number: int):
# Виконайте деякі асинхронні дії, щоб створити бургери
return burgers
```
...замість `def`:
```Python hl_lines="2"
# Це не асинхронно
def get_sequential_burgers(number: int):
# Виконайте деякі послідовні дії, щоб створити бургери
return burgers
```
З `async def` Python знає, що всередині цієї функції він має відслідковувати вирази `await`, і що він може «ставити на паузу» ⏸ виконання цієї функції і йти робити щось інше 🔀, перш ніж повернутися.
Коли ви хочете викликати функцію, визначену з `async def`, ви маєте «очікувати» її. Тож це не спрацює:
```Python
# Це не спрацює, тому що get_burgers визначено як: async def
burgers = get_burgers(2)
```
---
Отже, якщо ви використовуєте бібліотеку, яку можна викликати з `await`, вам потрібно створити функцію операції шляху, що її використовує, з `async def`, як тут:
```Python hl_lines="2-3"
@app.get('/burgers')
async def read_burgers():
burgers = await get_burgers(2)
return burgers
```
### Більше технічних деталей { #more-technical-details }
Ви могли помітити, що `await` можна використовувати лише всередині функцій, визначених з `async def`.
А водночас функції, визначені з `async def`, потрібно «очікувати». Тож функції з `async def` також можна викликати лише всередині функцій, визначених з `async def`.
Тож як же викликати першу `async`-функцію - курка чи яйце?
Якщо ви працюєте з **FastAPI**, вам не потрібно про це турбуватися, адже цією «першою» функцією буде ваша функція операції шляху, і FastAPI знатиме, як учинити правильно.
Але якщо ви хочете використовувати `async` / `await` без FastAPI, ви також можете це зробити.
### Пишемо свій власний async-код { #write-your-own-async-code }
Starlette (і **FastAPI**) базуються на <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, що робить їх сумісними як зі стандартною бібліотекою Python <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a>, так і з <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>.
Зокрема, ви можете безпосередньо використовувати <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> для ваших просунутих сценаріїв рівночасності, що потребують складніших патернів у вашому коді.
І навіть якщо ви не використовували FastAPI, ви могли б писати свої власні async-застосунки з <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a>, щоб мати високу сумісність і отримати його переваги (наприклад, *структурована рівночасність*).
Я створив іншу бібліотеку поверх AnyIO, як тонкий шар, щоб дещо покращити анотації типів і отримати кращу **автодопомогу** (autocompletion), **вбудовані помилки** (inline errors) тощо. Вона також має дружній вступ і навчальний посібник, щоб допомогти вам **зрозуміти** і написати **власний async-код**: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. Вона буде особливо корисною, якщо вам потрібно **поєднувати async-код зі звичайним** (блокуючим/синхронним) кодом.
### Інші форми асинхронного коду { #other-forms-of-asynchronous-code }
Такий стиль використання `async` і `await` відносно новий у мові.
Але він значно полегшує роботу з асинхронним кодом.
Такий самий (або майже ідентичний) синтаксис нещодавно з’явився в сучасних версіях JavaScript (у Browser і NodeJS).
До цього робота з асинхронним кодом була значно складнішою.
У попередніх версіях Python ви могли використовувати потоки (threads) або <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>. Але код набагато складніший для розуміння, налагодження і мислення про нього.
У попередніх версіях NodeJS/Browser JavaScript ви б використовували «callbacks», що призводить до «callback hell».
## Співпрограми { #coroutines }
**Співпрограма** - це просто дуже вишукана назва для об’єкта, який повертає функція `async def`. Python знає, що це щось на кшталт функції, яку можна запустити і яка завершиться в певний момент, але яку також можна поставити на паузу ⏸ всередині, коли є `await`.
Але всю цю функціональність використання асинхронного коду з `async` і `await` часто підсумовують як використання «співпрограм». Це порівняно з головною ключовою особливістю Go - «Goroutines».
## Висновок { #conclusion }
Погляньмо на ту саму фразу ще раз:
> Сучасні версії Python мають підтримку «асинхронного коду» за допомогою так званих «співпрограм», з синтаксисом **`async` і `await`**.
Тепер це має більше сенсу. ✨
Усе це приводить у дію FastAPI (через Starlette) і дає йому таку вражаючу продуктивність.
## Дуже технічні деталі { #very-technical-details }
/// warning | Попередження
Ймовірно, ви можете пропустити це.
Це дуже технічні деталі про те, як **FastAPI** працює «під капотом».
Якщо у вас є чимало технічних знань (співпрограми, потоки, блокування тощо) і вам цікаво, як FastAPI обробляє `async def` проти звичайного `def`, - вперед.
///
### Функції операції шляху { #path-operation-functions }
Коли ви оголошуєте функцію операції шляху зі звичайним `def` замість `async def`, вона виконується у зовнішньому пулі потоків (threadpool), який потім «очікується», замість прямого виклику (оскільки прямий виклик блокував би сервер).
Якщо ви прийшли з іншого async-фреймворку, який не працює так, як описано вище, і звикли визначати тривіальні, лише обчислювальні функції операції шляху зі звичайним `def` заради крихітного виграшу у продуктивності (близько 100 наносекунд), зверніть увагу, що у **FastAPI** ефект буде протилежним. У таких випадках краще використовувати `async def`, якщо тільки ваші функції операції шляху не використовують код, що виконує блокуюче <abbr title="Input/Output - Ввід/Вивід: читання або запис на диск, мережеві комунікації.">I/O</abbr>.
Втім, у будь-якій ситуації є велика ймовірність, що **FastAPI** [все одно буде швидшим](index.md#performance){.internal-link target=_blank} (або принаймні порівнянним) за ваш попередній фреймворк.
### Залежності { #dependencies }
Те саме стосується і [залежностей](tutorial/dependencies/index.md){.internal-link target=_blank}. Якщо залежність є стандартною функцією `def` замість `async def`, вона виконується у зовнішньому пулі потоків.
### Підзалежності { #sub-dependencies }
Ви можете мати кілька залежностей і [підзалежностей](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank}, які вимагають одна одну (як параметри визначень функцій). Деякі з них можуть бути створені з `async def`, а деякі - зі звичайним `def`. Все працюватиме, і ті, що створені зі звичайним `def`, будуть викликані у зовнішньому потоці (з пулу потоків), а не «очікувані».
### Інші допоміжні функції { #other-utility-functions }
Будь-яка інша допоміжна функція, яку ви викликаєте безпосередньо, може бути створена зі звичайним `def` або `async def`, і FastAPI не впливатиме на спосіб її виклику.
Це відрізняється від функцій, які FastAPI викликає за вас: функції операції шляху і залежності.
Якщо ваша допоміжна функція є звичайною функцією з `def`, її буде викликано безпосередньо (як ви написали у своєму коді), не в пулі потоків; якщо функція створена з `async def`, тоді вам слід використовувати `await` при її виклику у вашому коді.
---
Знову ж таки, це дуже технічні деталі, які, ймовірно, стануть у пригоді, якщо ви спеціально їх шукали.
Інакше вам вистачить настанов із розділу вище: <a href="#in-a-hurry">Поспішаєте?</a>.