Django使用asyncio异步发邮件报SynchronousOnlyOperation错误如何解决
问题原因
从错误栈可以直接定位,报错触发在代码第一行if request.user.profile.role == 'sa' or request.user.profile.role == 'e':的位置,并非你后续发邮件、查询用户的步骤出错。Django的request.user是SimpleLazyObject懒加载对象,首次访问时会同步查询session数据库加载用户数据,异步上下文内直接调用同步数据库操作就会抛出SynchronousOnlyOperation错误。
需要用sync_to_async包裹的操作
所有同步IO相关的操作都需要用sync_to_async包裹,包括但不限于:
- 所有数据库读写操作:ORM查询、表单
save操作、懒加载对象的首次访问 - Django内置同步方法:
messages.success/warning、render_to_string、redirect等 - 邮件发送、文件IO等其他同步阻塞操作
你代码里查询User、包裹邮件发送的操作是正确的,错误是遗漏了其他同步操作的异步适配。
修正后的代码示例
from asgiref.sync import sync_to_async import asyncio async def new(request): # 异步加载用户角色,避免懒加载触发同步查询 def get_user_role(): return request.user.profile.role role = await sync_to_async(get_user_role)() if role == 'sa' or role == 'e': if request.method == "GET": raise Http404 elif request.method == "POST": form = TaskForm(request.POST) # 表单验证是同步操作,需包裹 is_valid = await sync_to_async(form.is_valid)() if is_valid: # 表单保存是同步数据库操作,需包裹 task = await sync_to_async(form.save)() subject = "New Task" # 模板渲染是同步操作,需包裹 def render_email_content(): return render_to_string('new_task_email.html', {'task': task}) message = await sync_to_async(render_email_content)() user = await sync_to_async(User.objects.get)(id=task.assign_to.id) send_mail_async = sync_to_async(user.email_user) asyncio.create_task(send_mail_async(subject, message, EMAIL_HOST_USER)) # messages相关方法是同步操作,需包裹 await sync_to_async(messages.success)(request, "New Task has ceated") else: await sync_to_async(messages.warning)(request, "Please Enter a valid data") # redirect是同步操作,需包裹 return await sync_to_async(redirect)('tasks') else: raise Http404
额外注意点
使用asyncio.create_task发送邮件时需要注意,若异步视图在邮件任务执行完成前就返回,部分部署环境会销毁请求上下文导致邮件发送失败,若出现该问题可以换成await等待邮件发送完成,或者接入专门的异步任务队列(如Celery)处理邮件类异步任务。
内容的提问来源于stack exchange,提问作者Maninder Singh
相关产品推荐
相关产品推荐

