1 changed files with 426 additions and 0 deletions
@ -0,0 +1,426 @@ |
|||
# Concurrency و async / await |
|||
|
|||
این بخش به توضیح سینتکس `async def` برای *path operation functions* و مقدمهای بر asynchronous code، concurrency و parallelism میپردازد. |
|||
|
|||
## عجله دارید ؟ |
|||
|
|||
<abbr title="طولانیه; نخونیدش "><strong>TL;DR:</strong></abbr> |
|||
|
|||
اگر از کتابخانههای third party استفاده میکنید که نیاز دارند با استفاده از `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 |
|||
توجه داشته باشید که تنها در توابعی که با `async def` ساخته شدهاند میتوانید از `await` استفاده کنید. |
|||
/// |
|||
|
|||
--- |
|||
|
|||
اگر از کتابخانهای استفاده میکنید که با یک منبع (مانند دیتابیس، API، فایل سیستم و ...) ارتباط برقرار میکند و از استفاده از `await` پشتیبانی نمیکند (که در حال حاضر برای اکثر کتابخانههای دیتابیس رایج است)، آنگاه توابع عملیات مسیر خود را بهصورت عادی و با استفاده از `def` تعریف کنید، مانند: |
|||
|
|||
```Python hl_lines="2" |
|||
@app.get('/') |
|||
def results(): |
|||
results = some_library() |
|||
return results |
|||
``` |
|||
|
|||
--- |
|||
|
|||
اگر برنامهی شما (به هر دلیلی) نیازی به ارتباط با منابع خارجی و انتظار برای پاسخ ندارد، از `async def` استفاده کنید. |
|||
|
|||
--- |
|||
|
|||
و در صورتی که مطمئن نیستید، از تعریف معمولی `def` بهره ببرید. |
|||
|
|||
--- |
|||
|
|||
**Note**: شما میتوانید به اندازه دلخواه در توابع عملیات مسیر از ترکیب `def` و `async def` استفاده کنید و هر کدام را به بهترین شکل ممکن تعریف کنید. FastAPI بهطور هوشمند با این ترکیبها رفتار میکند. |
|||
|
|||
به هر حال، در هر یک از موارد بالا، FastAPI همچنان بهصورت asynchronous کار کرده و عملکرد بسیار سریعی دارد؛ اما با پیروی از دستورالعملهای فوق، بهینهسازیهای عملکردی بیشتری نیز حاصل میشود. |
|||
|
|||
## جزئیات فنی |
|||
|
|||
نسخههای مدرن پایتون از asynchronous code با استفاده از مفهومی به نام **coroutines** و سینتکس **`async` و `await`** پشتیبانی میکنند. |
|||
|
|||
بگذارید این موضوع را در بخشهای زیر بررسی کنیم: |
|||
|
|||
* **Asynchronous Code** |
|||
* **`async` و `await`** |
|||
* **Coroutines** |
|||
|
|||
## Asynchronous کد |
|||
|
|||
Asynchronous code به این معناست که زبان برنامهنویسی میتواند به کامپیوتر/برنامه بگوید که در نقطهای از کد، باید منتظر تکمیل یک کار دیگر شود. فرض کنید آن کار "slow-file" نامیده شود. |
|||
|
|||
در این مدت، کامپیوتر میتواند کار دیگری انجام دهد در حالی که "slow-file" مشغول انجام وظیفه است. |
|||
|
|||
سپس، هر زمان که فرصت یافت، کامپیوتر/برنامه برمیگردد؛ یا وقتی که تمام کارهایش به پایان رسیده، بررسی میکند که آیا وظایفی که منتظر آنها بوده، تکمیل شدهاند و سپس کار مربوط به آنها را ادامه میدهد. |
|||
|
|||
به عنوان مثال، اولین وظیفهای که تکمیل میشود (مثلاً "slow-file") انتخاب شده و ادامهی پردازش روی آن انجام میشود. |
|||
|
|||
این "انتظار برای چیز دیگری" معمولاً به عملیاتهای **I/O** اشاره دارد که نسبت به سرعت پردازنده و حافظهی RAM نسبتاً کند هستند؛ مانند انتظار برای: |
|||
|
|||
* ارسال داده از سمت client به شبکه |
|||
* دریافت دادهای که برنامه شما ارسال کرده از طریق شبکه به client |
|||
* خواندن محتوای یک فایل از دیسک توسط سیستم و ارائه آن به برنامه شما |
|||
* نوشتن دادههایی که برنامه شما به سیستم داده است بر روی دیسک |
|||
* انجام یک عملیات API از راه دور |
|||
* تکمیل یک عملیات دیتابیس |
|||
* دریافت نتایج یک query دیتابیس |
|||
* و غیره. |
|||
|
|||
از آنجا که بیشتر زمان اجرا صرف انتظار برای عملیاتهای **I/O** میشود، این عملیاتها "I/O bound" نامیده میشوند. |
|||
|
|||
این سیستم asynchronous نامیده میشود چون برنامه مجبور نیست دقیقا synchronized با کار کند باشد و منتظر لحظهی دقیق پایان آن بماند تا نتیجه را بگیرد؛ بلکه پس از اتمام کار، میتواند برای چند میکروثانیه در صف بماند تا زمانی که کار دیگری به پایان برسد و سپس دوباره به کار خود ادامه دهد. |
|||
|
|||
برای کارهای synchronous (در مقابل asynchronous) معمولاً از واژهی "sequential" استفاده میشود، زیرا در این حالت، برنامه تمامی مراحل را به ترتیب انجام داده و سپس به وظیفهی بعدی میپردازد، حتی اگر این مراحل شامل زمان انتظار نیز شوند. |
|||
|
|||
### همزمانی و برگر ( Concurrency and Burgers) |
|||
|
|||
این مفهوم asynchronous code که توضیح داده شد، گاهی به عنوان **concurrency** نیز شناخته میشود. لازم به ذکر است که concurrency با parallelism متفاوت است. |
|||
|
|||
هر دو مفهوم به "اتفاق افتادن چند کار بهطور همزمان" اشاره دارند، اما جزئیات بین آنها بسیار متفاوت است. |
|||
|
|||
برای درک تفاوت، داستان زیر دربارهی burgers را تصور کنید: |
|||
|
|||
### همبرگرهای همزمان (Concurrent Burgers) |
|||
|
|||
فرض کنید با crush خود به فستفود میروید. در صف ایستادهاید در حالی که cashier سفارشهای مشتریان جلوی شما را میگیرد. 😍 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration"> |
|||
|
|||
سپس نوبت شما میشود؛ سفارش ۲ burgers بسیار خاص برای crush و خودتان میدهید. 🍔🍔 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration"> |
|||
|
|||
Cashier به cook در پشت صحنه میگوید که باید burgers شما را آماده کند (حتی اگر در حال حاضر burgers سفارش مشتریان قبلی را آماده میکنند). |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration"> |
|||
|
|||
شما پرداخت میکنید. 💸 |
|||
|
|||
Cashier شماره نوبت شما را به شما میدهد. |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration"> |
|||
|
|||
در حالی که منتظر هستید، با crush خود به سمت یک میز میروید، مینشینید و برای مدت طولانی گفتگو میکنید (چون burgers وقت میبرند تا آماده شوند). |
|||
|
|||
هنگامی که نشستهاید و منتظر burgers هستید، میتوانید از این زمان برای تحسین اینکه crush شما چقدر باحال، بامزه و باهوش است استفاده کنید. ✨😍✨ |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration"> |
|||
|
|||
در حین انتظار و صحبت با crush، گهگاهی شمارهای که روی صفحه نمایش قرار دارد را بررسی میکنید تا ببینید نوبت شما فرا رسیده یا خیر. |
|||
|
|||
سپس در نهایت نوبت شما میشود. به پیشخوان میروید، burgers خود را دریافت میکنید و به میز بازمیگردید. |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration"> |
|||
|
|||
شما و crush burgers را میخورید و از لحظات خوش لذت میبرید. ✨ |
|||
|
|||
/// info |
|||
تصاویر زیبا با افتخار توسط <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a> ارائه شده است. 🎨 |
|||
/// |
|||
|
|||
--- |
|||
|
|||
تصور کنید شما در این داستان به عنوان یک برنامه هستید. |
|||
|
|||
در حالی که در صف هستید، کاملاً idle بوده و منتظر نوبت خود میمانید، بدون اینکه کار بسیار "productive" انجام دهید. اما صف سریع است چون cashier تنها سفارشها را میگیرد (نه آمادهسازی آنها)؛ بنابراین مشکلی نیست. |
|||
|
|||
سپس وقتی نوبت شما میشود، کارهای واقعی و "productive" انجام میدهید؛ منو را بررسی میکنید، تصمیم میگیرید چه میخواهید، انتخاب crush را میگیرید، پرداخت میکنید، چک میکنید که قبض یا کارت درست ارائه شده، اطمینان حاصل میکنید که مبلغ به درستی شارژ شده و سفارش شامل موارد صحیح است و غیره. |
|||
|
|||
اما سپس، حتی اگر هنوز burgers خود را دریافت نکرده باشید، کار شما با cashier "on pause" میشود، زیرا باید منتظر باشید تا burgers آماده شوند. |
|||
|
|||
اما زمانی که از پیشخوان دور میشوید و به میز مینشینید (با شماره نوبت در دست)، میتوانید توجه خود را به crush تغییر دهید و روی آن "work" کنید. سپس دوباره کاری "productive" مانند flirting با crush انجام میدهید. |
|||
|
|||
بعد، cashier اعلام میکند که "Burgers آماده شدند" و شمارهی شما را روی نمایشگر قرار میدهد؛ اما شما بلافاصله واکنش نشان نمیدهید، زیرا میدانید هیچکس burgers شما را نخواهد گرفت چون شماره نوبت شما مشخص است. |
|||
|
|||
پس منتظر میمانید تا crush داستان خود را تمام کند (یا کار فعلی به پایان برسد)، به آرامی لبخند میزنید و اعلام میکنید که میروید burgers را دریافت کنید. |
|||
|
|||
سپس به پیشخوان باز میگردید، کار اولیه که اکنون به پایان رسیده را تکمیل میکنید، burgers را دریافت میکنید، تشکر میکنید و آنها را به میز میبرید. این کار آن مرحله از تعامل با پیشخوان را به پایان میرساند و در عوض، وظیفه جدید "خوردن burgers" ایجاد میکند؛ در حالی که کار قبلی "دریافت burgers" به پایان رسیده است. |
|||
|
|||
### برگرهای موازی (Parallel Burgers) |
|||
|
|||
حال تصور کنید اینها "Concurrent Burgers" نیستند بلکه "Parallel Burgers" هستند. |
|||
|
|||
فرض کنید با crush خود برای دریافت fast food به یک محل میروید. |
|||
|
|||
در صف ایستادهاید، در حالی که چندین cashier (مثلاً ۸ نفر) که همزمان به عنوان cook عمل میکنند، سفارشهای مشتریان جلوی شما را میگیرند. |
|||
|
|||
همهی مشتریان جلوی شما منتظر میمانند تا burgers آنها آماده شود و قبل از ترک پیشخوان، هر کدام cashier مربوطه به سرعت سفارش را آماده میکند و سپس سفارش بعدی را میگیرد. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration"> |
|||
|
|||
سپس بالاخره نوبت شما میشود؛ سفارش ۲ burgers بسیار خاص برای crush و خودتان میدهید. |
|||
|
|||
شما پرداخت میکنید. 💸 |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration"> |
|||
|
|||
Cashier به آشپزخانه میرود. |
|||
|
|||
شما منتظر میمانید؛ در حالی که جلوی پیشخوان ایستادهاید 🕙 تا هیچکس burgers شما را قبل از شما نگیرد، چون شماره نوبت وجود ندارد. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration"> |
|||
|
|||
در حالی که شما و crush مشغول جلوگیری از عبور دیگران و دزدیدن burgers هستید، نمیتوانید توجه خود را به crush معطوف کنید. 😞 |
|||
|
|||
این حالت، کار synchronous است؛ شما کاملاً با cashier/cook هماهنگ هستید 👨🍳. باید منتظر بمانید و در لحظهی دقیق اتمام burgers و تحویل آنها حضور داشته باشید، در غیر این صورت ممکن است کسی دیگری آنها را بگیرد. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration"> |
|||
|
|||
سپس cashier/cook پس از مدت طولانی انتظار، burgers شما را برمیگرداند. |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration"> |
|||
|
|||
شما burgers را دریافت کرده و به میز با crush میروید. |
|||
|
|||
فقط burgers را میخورید و کارتان به پایان میرسد. ⏹ |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration"> |
|||
|
|||
در این حالت، صحبت یا flirting چندانی وجود ندارد چرا که بیشتر زمان صرف انتظار جلوی پیشخوان شده است. 😞 |
|||
|
|||
/// info |
|||
تصاویر زیبا با افتخار توسط <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a> ارائه شده است. 🎨 |
|||
/// |
|||
|
|||
--- |
|||
|
|||
در این سناریو برای Parallel Burgers، شما به عنوان یک برنامه با دو processor (شما و crush) هستید که هر دو برای مدت طولانی منتظر میمانند تا "روی پیشخوان" حضور داشته باشند. |
|||
|
|||
فستفود مذکور دارای ۸ processor (cashier/cook) است، در حالی که فروشگاه Concurrent Burgers ممکن است تنها دارای ۲ processor (یک cashier و یک cook) باشد. |
|||
|
|||
اما باز هم تجربه نهایی مطلوب نیست. 😞 |
|||
|
|||
--- |
|||
|
|||
این داستان معادل Parallel برای burgers است. 🍔 |
|||
|
|||
برای یک مثال "real life" از این موضوع، تصور کنید که در یک بانک هستید. |
|||
|
|||
تا چندی پیش، اکثر بانکها دارای چندین cashier 👨💼👨💼👨💼👨💼 و صفهای طولانی بودند 🕙🕙🕙🕙🕙🕙🕙🕙. |
|||
|
|||
تمام cashierها یکی پس از دیگری با یک مشتری کار میکردند 👨💼⏯. |
|||
|
|||
و شما مجبور بودید برای مدت طولانی در صف بمانید تا نوبتتان برسد، در غیر این صورت نوبتتان از دست میرفت. |
|||
|
|||
احتمالاً نمیخواستید crush خود را همراه داشته باشید تا به بانک بروید. 🏦😍 |
|||
|
|||
### نتیجه گیری برگر (Burger Conclusion) |
|||
|
|||
در این سناریو از "fast food burgers with your crush"، به دلیل وجود زمانهای طولانی انتظار، استفاده از یک سیستم concurrent بسیار منطقیتر به نظر میرسد. |
|||
|
|||
این حالت برای اکثر برنامههای وب صادق است. |
|||
|
|||
کاربران زیادی وجود دارند، اما سرور شما منتظر connectionهای ضعیف آنها برای ارسال requestها میماند. |
|||
|
|||
سپس دوباره منتظر پاسخها میماند. |
|||
|
|||
این "waiting" در میکروثانیه اندازهگیری میشود، اما در مجموع، زمان انتظار بسیار زیاد است. |
|||
|
|||
به همین دلیل است که استفاده از asynchronous code برای web APIs بسیار منطقی به نظر میرسد. |
|||
|
|||
همین نوع asynchronous است که باعث محبوبیت NodeJS شد (حتی اگر NodeJS parallel نباشد) و همینطور نقطه قوت زبان برنامهنویسی Go محسوب میشود. |
|||
|
|||
همچنین با داشتن همزمان parallelism و asynchronicity، عملکرد بالاتری نسبت به اکثر چارچوبهای آزمایششدهی NodeJS به دست میآورید که با Go (یک زبان compiled نزدیک به 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>) قابل رقابت است. |
|||
|
|||
### آیا concurrency بهتر از parallelism است؟ |
|||
|
|||
خیر! این پیام اصلی داستان نیست. |
|||
|
|||
Concurrency با parallelism متفاوت است و در سناریوهای **خاص** که شامل زمان انتظار زیاد هستند، عملکرد بهتری دارد. به همین دلیل، برای توسعهی برنامههای وب بهطور کلی concurrency بهتر از parallelism است؛ اما نه برای همه موارد. |
|||
|
|||
برای توضیح بیشتر، داستان کوتاه زیر را تصور کنید: |
|||
|
|||
> شما باید یک خانهی بزرگ و کثیف را تمیز کنید. |
|||
|
|||
*بله، این تمام داستان است.* |
|||
|
|||
--- |
|||
|
|||
در این حالت جایی برای waiting وجود ندارد، فقط مقدار زیادی کار در نقاط مختلف خانه باید انجام شود. |
|||
|
|||
ممکن است بتوانید همانند مثال burgers، ابتدا اتاق نشیمن و سپس آشپزخانه را تمیز کنید؛ اما از آنجا که در این کار نیازی به waiting نیست و فقط تمیزکاری میکنید، نوبتبندی تأثیری نخواهد داشت. |
|||
|
|||
مدت زمان لازم برای اتمام کار با یا بدون turns (concurrency) تقریباً یکسان خواهد بود و مقدار کاری که انجام میدهید نیز تغییر نخواهد کرد. |
|||
|
|||
اما در این حالت، اگر بتوانید ۸ نفر (مانند cashier/cook سابق که حالا cleaner هستند) همراه داشته باشید و هر کدام (به همراه شما) یک ناحیه از خانه را به عهده بگیرند، میتوانید تمام کار را بهصورت parallel انجام دهید و با کمک اضافی، خیلی زودتر کار تمام شود. |
|||
|
|||
در این سناریو، هر یک از cleaners (از جمله شما) مانند یک processor عمل میکنند و هر کدام قسمت مشخصی از کار را انجام میدهند. |
|||
|
|||
و از آنجا که بیشتر زمان اجرا صرف کار واقعی (به جای waiting) میشود و کار در کامپیوتر توسط <abbr title="Central Processing Unit">CPU</abbr> انجام میشود، این مسائل "CPU bound" نامیده میشوند. |
|||
|
|||
--- |
|||
|
|||
مثالهای رایج برای عملیاتهای CPU bound، مسائلی هستند که نیاز به پردازش ریاضی پیچیده دارند. |
|||
|
|||
برای مثال: |
|||
|
|||
* **Audio** یا **image processing**. |
|||
* **Computer vision**: یک تصویر از میلیونها پیکسل تشکیل شده است؛ هر پیکسل شامل ۳ value/رنگ است و پردازش آن معمولاً نیاز به انجام محاسباتی بر روی این پیکسلها بهطور همزمان دارد. |
|||
* **Machine Learning**: معمولاً به ضربهای فراوان matrix و vector نیاز دارد. تصور کنید یک spreadsheet عظیم با اعداد که همهی آنها بهطور همزمان در حال ضرب شدن هستند. |
|||
* **Deep Learning**: که زیرشاخهای از Machine Learning است؛ در اینجا نیز همان موضوع صدق میکند. تفاوت اینجاست که تنها یک spreadsheet برای ضرب وجود ندارد بلکه مجموعهی عظیمی از اعداد وجود دارد و در بسیاری از موارد از پردازندههای ویژهای برای ساخت و/یا استفاده از این مدلها بهره گرفته میشود. |
|||
|
|||
### Concurrency + Parallelism: Web + Machine Learning |
|||
|
|||
با **FastAPI** شما میتوانید از concurrency بهره ببرید که در توسعه وب بسیار رایج است (همان ویژگی جذاب NodeJS). |
|||
|
|||
اما همچنین میتوانید از مزایای parallelism و multiprocessing (اجرای چند فرآیند بهطور موازی) برای کارهای **CPU bound** مانند سیستمهای Machine Learning بهره ببرید. |
|||
|
|||
این موضوع به علاوهی این واقعیت ساده است که Python زبان اصلی Data Science، Machine Learning و بهویژه Deep Learning محسوب میشود، FastAPI را گزینهی بسیار مناسبی برای APIها و اپلیکیشنهای وب مربوط به Data Science/Machine Learning میسازد (در میان بسیاری دیگر). |
|||
|
|||
برای مشاهده نحوهی دستیابی به این parallelism در محیط production، به بخش [Deployment](deployment/index.md){.internal-link target="_blank"} مراجعه کنید. |
|||
|
|||
## `async` و `await` |
|||
|
|||
نسخههای مدرن Python روشی بسیار شهودی برای تعریف asynchronous code ارائه میدهند. این موضوع باعث میشود کد کاملاً شبیه به کد sequential به نظر برسد و عملیات waiting در لحظات مناسب به طور خودکار انجام شود. |
|||
|
|||
زمانی که عملیاتی وجود دارد که قبل از ارائهی نتیجه نیاز به waiting دارد و از این ویژگیهای جدید پشتیبانی میکند، میتوانید آن را به این صورت بنویسید: |
|||
|
|||
```Python |
|||
burgers = await get_burgers(2) |
|||
``` |
|||
|
|||
کلید اینجا استفاده از `await` است. این کلمه به Python میگوید که باید ⏸ منتظر بماند تا `get_burgers(2)` کار خود را به اتمام برساند و سپس نتیجه را در متغیر `burgers` ذخیره کند. بدین ترتیب، Python میداند که در همین حین میتواند کار دیگری انجام دهد (مثلاً دریافت request دیگری). |
|||
|
|||
برای اینکه `await` کار کند، لازم است که در داخل تابعی استفاده شود که از asynchronous بودن پشتیبانی میکند. برای این منظور، کافیست تابع را با `async def` تعریف کنید: |
|||
|
|||
```Python hl_lines="1" |
|||
async def get_burgers(number: int): |
|||
# انجام کارهای asynchronous برای آمادهسازی burgers |
|||
return burgers |
|||
``` |
|||
|
|||
...به جای: |
|||
|
|||
```Python hl_lines="2" |
|||
# این کد asynchronous نیست |
|||
def get_sequential_burgers(number: int): |
|||
# انجام کارهای sequential برای آمادهسازی burgers |
|||
return burgers |
|||
``` |
|||
|
|||
با استفاده از `async def`، Python میداند که در داخل آن تابع باید از عبارات `await` آگاه باشد و میتواند اجرای آن تابع را pause داده و قبل از بازگشت، کار دیگری انجام دهد. |
|||
|
|||
هنگامی که میخواهید از تابعی که با `async def` تعریف شده فراخوانی کنید، باید آن را "await" کنید. به عنوان مثال، کد زیر کار نخواهد کرد: |
|||
|
|||
```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 |
|||
``` |
|||
|
|||
### جزئیات فنی بیشتر |
|||
|
|||
شاید متوجه شده باشید که `await` تنها در داخل توابع تعریفشده با `async def` قابل استفاده است. |
|||
|
|||
اما در عین حال، توابع تعریفشده با `async def` نیز باید "await" شوند؛ یعنی این توابع تنها در داخل توابع دیگری که با `async def` تعریف شدهاند قابل فراخوانی هستند. |
|||
|
|||
پس سوال پیش میآید: دربارهی مسئلهی "egg and chicken"، چگونه اولین تابع `async` را فراخوانی کنیم؟ |
|||
|
|||
اگر با **FastAPI** کار میکنید، نیازی به نگرانی ندارید؛ چرا که اولین تابع شما همان path operation function خواهد بود و FastAPI میداند چگونه کار را انجام دهد. |
|||
|
|||
اما اگر میخواهید بدون FastAPI از `async`/`await` استفاده کنید، همچنان میتوانید این کار را انجام دهید. |
|||
|
|||
### نوشتن کد asynchronous خودتان |
|||
|
|||
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> برای سناریوهای پیشرفته concurrency استفاده کنید که نیاز به الگوهای پیچیدهتر در کد شما دارد. |
|||
|
|||
و حتی اگر از FastAPI استفاده نمیکنید، میتوانید اپلیکیشنهای asynchronous خود را با <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> بنویسید تا سازگاری بالا و مزایای آن (مانند structured concurrency) را دریافت کنید. |
|||
|
|||
من همچنین یک کتابخانهی دیگر به عنوان یک لایهی نازک بر روی AnyIO ایجاد کردهام تا type annotations را بهبود بخشم و **autocompletion** و **inline errors** بهتری ارائه دهم. این کتابخانه همچنین دارای معرفی و آموزش دوستانهای برای کمک به شما در درک و نوشتن کد asynchronous خودتان است: <a href="https://asyncer.tiangolo.com/" class="external-link" target="_blank">Asyncer</a>. این کتابخانه بهویژه برای زمانی مفید است که نیاز به ترکیب کد asynchronous با کد عادی (blocking/synchronous) داشته باشید. |
|||
|
|||
### اشکال دیگر کد asynchronous |
|||
|
|||
این سبک استفاده از `async` و `await` نسبتاً جدید در Python است. |
|||
|
|||
اما باعث میشود کار با asynchronous code بسیار سادهتر شود. |
|||
|
|||
این سینتکس (یا تقریباً مشابه آن) اخیراً در نسخههای مدرن JavaScript (در Browser و NodeJS) نیز گنجانده شده است. |
|||
|
|||
اما پیش از آن، مدیریت asynchronous code بسیار پیچیدهتر و دشوارتر بود. |
|||
|
|||
در نسخههای قبلی Python، میتوانستید از threads یا <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a> استفاده کنید؛ اما کد بسیار پیچیدهتری برای درک، رفع اشکال و تفکر نیاز داشت. |
|||
|
|||
در نسخههای قبلی NodeJS/Browser JavaScript، از "callbacks" استفاده میکردند که به اصطلاح منجر به "callback hell" میشد. |
|||
|
|||
## Coroutines |
|||
|
|||
**Coroutine** اصطلاحی بسیار شیک برای شیئی است که توسط تابعی که با `async def` تعریف شده بازگردانده میشود. Python میداند که این شیء شبیه یک تابع است که میتواند شروع شود و در نهایت به پایان برسد، اما ممکن است بهطور داخلی نیز در لحظاتی که `await` وجود دارد، pause کند. |
|||
|
|||
تمام این قابلیتهای استفاده از asynchronous code با `async` و `await` اغلب به صورت "coroutines" خلاصه میشود. این مفهوم قابل مقایسه با ویژگی اصلی در Go، یعنی "Goroutines" است. |
|||
|
|||
## Conclusion |
|||
|
|||
بیایید همان جمله بالا را مرور کنیم: |
|||
|
|||
> نسخههای مدرن Python از asynchronous code با استفاده از مفهومی به نام coroutines و سینتکس `async` و `await` پشتیبانی میکنند. |
|||
|
|||
امیدواریم اکنون برای شما معنای این موضوع واضحتر شده باشد. ✨ |
|||
|
|||
همین است که FastAPI (از طریق Starlette) را به آنچه که عملکرد بسیار چشمگیری دارد، مجهز میکند. |
|||
|
|||
## جزئیات بسیار فنی |
|||
|
|||
/// warning |
|||
شاید بتوانید این بخش را رد بزنید. |
|||
اینها جزئیات بسیار فنی دربارهی نحوهی عملکرد درونی FastAPI هستند. |
|||
|
|||
اگر دانش فنی کافی (مانند coroutines، threads، blocking و غیره) دارید و کنجکاو هستید که بدانید FastAPI چگونه با `async def` در مقابل `def` رفتار میکند، ادامه دهید. |
|||
/// |
|||
|
|||
### Path operation functions |
|||
|
|||
وقتی یک path operation function را با استفاده از `def` معمولی به جای `async def` تعریف میکنید، آن تابع در یک threadpool خارجی اجرا میشود که سپس await میشود، به جای اینکه بهصورت مستقیم فراخوانی شود (چون این کار میتواند باعث blocking سرور شود). |
|||
|
|||
اگر از چارچوب asynchronous دیگری آمدهاید که به شیوه توضیح دادهشده کار نمیکند و عادت دارید توابع عملیات مسیر محض محاسباتی را با `def` تعریف کنید (برای به دست آوردن افزایش جزئی عملکرد به اندازهای حدود ۱۰۰ نانوثانیه)، توجه داشته باشید که در FastAPI اثر دقیقا برعکس خواهد بود. در این موارد بهتر است از `async def` استفاده کنید مگر اینکه توابع عملیات مسیر شما از کدی استفاده کنند که عملیات blocking (I/O) انجام میدهد. |
|||
|
|||
با این حال، در هر دو حالت احتمالاً FastAPI همچنان [سریعتر خواهد بود](index.md#performance){.internal-link target="_blank"} (یا حداقل قابل مقایسه با) چارچوب قبلی شما. |
|||
|
|||
### وابستگی ها (Dependencies) |
|||
|
|||
همین موضوع برای [dependencies](tutorial/dependencies/index.md){.internal-link target="_blank"} نیز صادق است. اگر یک dependency بهصورت تابع `def` استاندارد تعریف شده باشد به جای `async def`، در threadpool خارجی اجرا میشود. |
|||
|
|||
### وابستگی های فرعی (Sub-dependencies) |
|||
|
|||
شما میتوانید چندین dependency و [sub-dependency](tutorial/dependencies/sub-dependencies.md){.internal-link target="_blank"} داشته باشید که به یکدیگر وابستهاند (به عنوان پارامترهای تعاریف توابع). برخی از آنها ممکن است با `async def` و برخی با `def` ساخته شوند. باز هم کار میکند و آنهایی که با `def` ساخته شدهاند در یک threadpool خارجی فراخوانی میشوند به جای اینکه await شوند. |
|||
|
|||
### سایر utility functions |
|||
|
|||
هر تابع utility دیگری که مستقیماً فراخوانی میکنید، میتواند با استفاده از `def` معمولی یا `async def` تعریف شود و FastAPI تاثیری در نحوهی فراخوانی آن ندارد. |
|||
|
|||
این موضوع در تضاد با توابعی است که FastAPI برای شما فراخوانی میکند: path operation functions و dependencies. |
|||
|
|||
اگر تابع utility شما بهصورت تابعی معمولی (`def`) تعریف شده باشد، مستقیماً (همانطور که در کد نوشتهاید) فراخوانی میشود و در threadpool اجرا نمیشود؛ اما اگر تابع با `async def` ساخته شده باشد، باید هنگام فراخوانی آن از `await` استفاده کنید. |
|||
|
|||
--- |
|||
|
|||
باز هم، اینها جزئیات بسیار فنی هستند که احتمالاً مفید خواهند بود اگر به دنبال آنها باشید. |
|||
|
|||
در غیر این صورت، کافی است از دستورالعملهای بخش In a hurry? پیروی کنید. |
|||
|
Loading…
Reference in new issue