اگه تازگیا وارد دنیای جذاب FastAPI شده باشی، احتمالا همون اول کار به یک دوراهی معروف رسیدی: "برای نوشتن روت‌ها (Routes) و توابعم از def استفاده کنم یا async def؟" خیلی‌ها فکر میکنند چون FastAPI یک فریمورک «آسنکرون» (Asynchronous) است، پس حتماً باید همه ‌چیز رو با async def بنویسند. اما ماجرا اصلاً این‌طور نیست! بیا با هم به زبان خیلی ساده و علمی این قضیه رو کالبد شکافی کنیم.

جادوی FastAPI در مدیریت توابع سنتی و مدرن

شاید فکر کنی اگر از def معمولی (همزمان یا Synchronous) استفاده کنی، کل سرور بلوکه میشه و سرعتش میفته. اما FastAPI انقدر هوشمنده که با هر کدوم از این دو حالت رفتار متفاوتی داره تا کارایی برنامه حفظ بشه:

۱. وقتی از async def استفاده میکنی: FastAPI این توابع رو مستقیماً میندازه توی چرخه ‌ی اصلی برنامه‌اش (Event Loop). در این حالت، برنامه انتظار داره که تو اگر کاری داری که طول میکشه (مثل خوندن از دیتابیس یا فراخوانی یک API دیگه)، حتماً از کلمه‌ی کلیدی await استفاده کنی. وقتی کُد به await میرسه، اجرای اون تابع موقتاً متوقف میشه و سرور میره سراغ پردازش درخواست ‌های بقیه کاربرها، تا زمانی که اون کارِ طولانی تموم بشه و جوابش برگرده.

۲. وقتی از def معمولی استفاده میکنی: FastAPI با خودش میگه: "آها! این تابع async نیست، پس ممکنه عملیاتش طول بکشه و اگر اون رو بفرستم تو چرخه اصلی، کل سرور رو قفل (Block) کنه." پس FastAPI چیکار میکنه؟ این تابع رو میندازه تو یک «استخرِ رشته‌های کاری» (Threadpool). یعنی یک تِردِ (Thread) جداگانه و رزرو شده رو به این تابع اختصاص میده تا کارش رو انجام بده. با این کار، چرخه‌ی اصلی برنامه آزاد میمونه و میتونه به بقیه درخواست ‌ها رسیدگی کنه.

نتیجه؟ در کمال تعجب، در فریمورک FastAPI استفاده از def معمولی سرور شما رو متوقف (Block) نمیکند، چون در پس‌زمینه به Threadpool منتقل میشه!

بالاخره کی از کدام استفاده کنیم؟ (قوانین طلایی)

حالا که فهمیدیم پشت صحنه چه خبره، بیا ببینیم تو پروژه‌ ات چطور باید تصمیم بگیری:

کی بریم سراغ async def؟

فقط زمانی ازش استفاده کن که کار I/O Bound (مثل کار با شبکه، دیتابیس یا فایل) داری و کتابخانه‌ای که استفاده میکنی هم از async پشتیبانی میکنه.

  • مثال‌های خوب: استفاده از کتابخانه httpx (برای درخواست‌های HTTP آسنکرون)، استفاده از دیتابیس‌های آسنکرون (مثل asyncpg برای پستگرس، Motor برای مونگو دی‌بی یا SQLAlchemy در حالت async).

  • نکته مهم: توی این توابع، حتماً باید از await برای کارهای طولانی استفاده کنی.

کِی بچسبیم به همون def خودمون؟

  • وقتی داری از کتابخانه‌هایی استفاده میکنی که آسنکرون نیستند و به صورت سنتی (Synchronous) نوشته شده‌اند.

  • مثال‌های خوب: استفاده از کتابخانه معروف requests برای ارتباط با APIها، کار با SQLAlchemy در حالت سنتی، یا زمانی که یک پردازش سنگین محاسباتی یا CPU-Bound داری (مثل تغییر سایز عکس، کار با مدل‌های هوش مصنوعی، یا پردازش‌های سنگین ریاضی).

تله‌ی مرگبار: بدترین اشتباهی که میتونی بکنی!

یک حالت بسیار خطرناک وجود داره که باعث میشه کل برنامه‌ی FastAPI شما سکته کنه و از کار بیفته: استفاده از کد سنتیِ بلاک‌کننده در داخل تابع async def

# ❌ یک فاجعه‌ی واقعی در کدنویسی FastAPI
@app.get("/bad-example")
async def bad_route():
    import time
    time.sleep(5) # این کار کل چرخه اصلی (Event Loop) سرور رو ۵ ثانیه فلج می‌کنه!
    return {"message": "Done"}

وقتی تو تابع رو با async def تعریف میکنی، به FastAPI قول میدی که هیچ کارِ بلاک‌کننده‌ای انجام ندی. اما با گذاشتن یک کد سینک (مثل time.sleep یا کتابخانه requests)، چرخه اصلی سرور رو مجبور میکنی تا اتمام اون کار معطل بمونه. در این حالت، سرور تو تا ۵ ثانیه به هیچ ریکوئست دیگه ‌ای از هیچ کاربر دیگه‌ای جواب نمیده!

راه‌حل؟ اگر مجبوری از کدهای سنتی و بلاک‌کننده استفاده کنی، کلمه‌ی async رو از ابتدای تابع بردار و اون رو به یک def معمولی تبدیل کن. به همین راحتی FastAPI خودش مشکل رو با فرستادن کد به Threadpool حل میکنه و سرور قفل نمیشه.

جمع بندی

  • اگر مطمئن نیستی یا کتابخونه‌هات قدیمی و سنتی هستند: از همون def معمولی استفاده کن. درسته که به خاطر استفاده از Threadpool یک مقدار خیلی کمی مصرف منابعش بیشتره، ولی کاملاً امنه و سرورت رو نمیخوابونه.

  • اگر می‌خوای نهایت سرعت، بازدهی و پرفورمنس رو داشته باشی: ابزارها و درایورهای مدرنِ async رو یاد بگیر و همه ‌چیز رو به صورت استاندارد با async def و await بنویس.