~6 دقیقه مطالعه • بروزرسانی ۲۴ اسفند ۱۴۰۴
مقدمه
Django از نسخههای اخیر خود پشتیبانی گستردهای از برنامهنویسی غیرهمزمان (async) ارائه میدهد. اگر پروژه را تحت ASGI اجرا کنید، کل زنجیرهٔ پردازش درخواست میتواند async باشد. در حالت WSGI نیز async views کار میکنند، اما با محدودیتهای عملکردی.
ORM و برخی بخشهای Django هنوز کاملاً async نشدهاند، اما با استفاده از sync_to_async() میتوان از آنها در محیط async استفاده کرد.
Async Views
هر view در Django میتواند async باشد. کافی است آن را با async def تعریف کنید:
async def my_view(request):
return HttpResponse("Hello async world!")
در class-based views، متدهایی مثل get() و post() باید async باشند.
Django از iscoroutinefunction برای تشخیص async بودن view استفاده میکند.
اگر روش سفارشی دارید، از markcoroutinefunction استفاده کنید.
Async Views تحت WSGI
در WSGI، هر async view در یک event loop جداگانه اجرا میشود. این یعنی async کار میکند، اما مزایای ASGI را ندارد.
Async Views تحت ASGI
اینجاست که قدرت async مشخص میشود:
- پشتیبانی از هزاران اتصال همزمان
- long-polling
- streaming responses
- درخواستهای طولانی بدون استفاده از thread
Middleware و async
برای داشتن یک stack کاملاً async، تمام middleware باید async-capable باشند. اگر حتی یک middleware synchronous باشد، Django مجبور است برای هر درخواست یک thread ایجاد کند.
برای بررسی این موضوع، لاگ django.request را فعال کنید و پیامهای زیر را جستجو کنید:
"Asynchronous handler adapted for middleware ..."
فراخوانی کد synchronous از async
بخشهایی از Django مانند ORM هنوز sync هستند.
برای استفادهٔ امن از آنها در async view باید از sync_to_async() استفاده کنید:
from asgiref.sync import sync_to_async
result = await sync_to_async(sync_function, thread_sensitive=True)(pk=123)
اگر کد sync را مستقیم در async فراخوانی کنید، Django خطای SynchronousOnlyOperation میدهد.
Decoratorهای سازگار با async
بسیاری از decoratorهای Django با async views سازگارند، از جمله:
- never_cache()
- csrf_exempt()
- require_GET()
- etag()
- last_modified()
- vary_on_headers()
- xframe_options_deny()
- و بسیاری دیگر
ORM غیرهمزمان
Django از نسخههای جدید، ORM async را نیز پشتیبانی میکند:
async for author in Author.objects.filter(name__startswith="A"):
book = await author.books.afirst()
ویژگیهای مهم
- تمام متدهای query که SQL اجرا میکنند نسخهٔ async با پیشوند a دارند
async forروی تمام QuerySetها پشتیبانی میشود- متدهای async مدل مانند
asave()وaset()
محدودیتها
- تراکنشها در async پشتیبانی نمیشوند
- CONN_MAX_AGE باید غیرفعال شود
- برای pooling از ابزارهای خارجی استفاده کنید
عملکرد (Performance)
هر بار که Django مجبور به تغییر context بین sync و async شود، حدود ۱ میلیثانیه overhead ایجاد میشود.
این اتفاق زمانی رخ میدهد که:
- async view تحت WSGI اجرا شود
- middleware sync بین ASGI و async view قرار گیرد
- middleware async روی view sync اعمال شود
در برخی پروژهها حتی کد کاملاً sync نیز تحت ASGI سریعتر اجرا میشود.
مدیریت قطع اتصال (Disconnect)
در درخواستهای طولانی، ممکن است کاربر قبل از پایان view قطع شود.
در این حالت asyncio.CancelledError رخ میدهد:
async def my_view(request):
try:
...
except asyncio.CancelledError:
# cleanup
raise
Async Safety
بخشهایی از Django async-safe نیستند و در صورت استفادهٔ اشتباه خطای SynchronousOnlyOperation میدهند.
راهحل
- کد sync را در یک تابع sync جداگانه بنویسید
- آن را با
sync_to_async()فراخوانی کنید
محیطهایی که ناخواسته async هستند
Jupyter و IPython بهطور پیشفرض event loop فعال دارند. برای غیرفعال کردن:
%autoawait off
برای فعالسازی دوباره:
%autoawait on
DJANGO_ALLOW_ASYNC_UNSAFE
اگر مجبور باشید sync code را در async اجرا کنید، میتوانید این هشدار را غیرفعال کنید:
import os
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
هشدار: این کار در محیطهای concurrent میتواند باعث فساد داده شود. هرگز در production استفاده نکنید.
جمعبندی
پشتیبانی async در Django امکان ساخت برنامههای بسیار مقیاسپذیر و همزمان را فراهم میکند—بهویژه تحت ASGI.
با async views، ORM غیرهمزمان، و استفادهٔ صحیح از sync_to_async() میتوانید سیستمهایی مدرن و کارآمد بسازید.
با گسترش پشتیبانی async در Django، آیندهٔ این قابلیتها روشنتر خواهد شد.
مقدمه
در پروژههای Django که از async استفاده میکنند، معمولاً نیاز دارید کد synchronous را از محیط async فراخوانی کنید یا برعکس. برای این کار، Django (از طریق کتابخانهٔ asgiref) دو تابع کلیدی ارائه میدهد:
- async_to_sync()
- sync_to_async()
این توابع ستون فقرات سازگاری بین async و sync در Django هستند.
async_to_sync()
تابع async_to_sync یک تابع async را گرفته و آن را به یک تابع synchronous تبدیل میکند.
نمونه استفاده
from asgiref.sync import async_to_sync
async def get_data():
...
sync_get_data = async_to_sync(get_data)
@async_to_sync
async def get_other_data():
...
نحوهٔ کار
- اگر event loop فعال باشد، تابع async در همان loop اجرا میشود.
- اگر event loop وجود نداشته باشد، یک loop جدید ساخته و پس از اجرا بسته میشود.
- تابع async همیشه در یک thread جداگانه اجرا میشود.
- مقادیر threadlocal و contextvars در هر دو جهت حفظ میشوند.
این تابع نسخهٔ قدرتمندتر asyncio.run() است و با sync_to_async() نیز سازگاری کامل دارد.
sync_to_async()
تابع sync_to_async یک تابع synchronous را گرفته و آن را به یک تابع async تبدیل میکند.
نمونه استفاده
from asgiref.sync import sync_to_async
async_function = sync_to_async(sync_function, thread_sensitive=False)
@sync_to_async
def sync_function():
...
حفظ threadlocal و contextvars
مانند async_to_sync، این تابع نیز مقادیر threadlocal را در هر دو جهت حفظ میکند.
دو حالت threading
- thread_sensitive=True (پیشفرض): تابع sync در همان thread اجرا میشود که سایر توابع thread_sensitive اجرا میشوند (معمولاً main thread).
- thread_sensitive=False: تابع sync در یک thread جدید اجرا میشود که پس از پایان کار بسته میشود.
هشدار: از نسخهٔ 3.3.0 asgiref مقدار پیشفرض thread_sensitive برابر True شده است.
چرا thread_sensitive مهم است؟
زیرا بسیاری از کتابخانهها (مثل درایورهای دیتابیس) باید در همان thread که ساخته شدهاند اجرا شوند. همچنین بخش زیادی از کد Django فرض میکند که در یک thread ثابت اجرا میشود.
به همین دلیل sync_to_async تضمین میکند که تمام کد sync در یک thread مشترک اجرا شود.
نکتهٔ مهم: عدم ارسال connection به sync_to_async
نباید اشیاء حساس به thread مانند connection دیتابیس را مستقیماً به sync_to_async بدهید:
await sync_to_async(connection.cursor)()
این کار باعث خطا میشود:
- SynchronousOnlyOperation در محیط async
- DatabaseError به دلیل استفاده از connection در thread اشتباه
راهحل صحیح
تمام عملیات دیتابیس را داخل یک تابع sync قرار دهید و فقط آن تابع را با sync_to_async فراخوانی کنید.
جمعبندی
توابع async_to_sync و sync_to_async ابزارهای حیاتی برای اجرای ایمن و سازگار کد sync و async در Django هستند. این توابع امکان استفادهٔ همزمان از ORM، middleware، و async views را فراهم میکنند بدون اینکه با مشکلات thread یا event loop مواجه شوید.
با درک صحیح این دو تابع، میتوانید پروژههای Django را بهصورت ترکیبی (sync + async) یا کاملاً async توسعه دهید.
نوشته و پژوهش شده توسط دکتر شاهین صیامی