【问题标题】:Django: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_asyncDjango:SynchronousOnlyOperation:您不能从异步上下文中调用它 - 使用线程或 sync_to_async
【发布时间】:2020-09-07 14:27:19
【问题描述】:

我使用运行 shell_plus --notebook 的 Django 3.0.6 和 Jupyter 笔记本。

我尝试运行查询集:

User.objects.all()

但是返回这个错误SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async

我试试这个命令

from asgiref.sync import sync_to_async

users = sync_to_async(User.objects.all())

for user in users:
    print(user)

TypeError: 'SyncToAsync' object is not iterable

Django文档的解决方案

settings.py 中的os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" 是唯一的解决方案吗?

【问题讨论】:

    标签: asynchronous jupyter django-3.0


    【解决方案1】:

    sync_to_async 接受一个可调用对象,而不是结果。相反,你想要这个:

    from asgiref.sync import sync_to_async
    
    users = sync_to_async(User.objects.all)()
    
    for user in users:
        print(user)
    

    您还可以将要包装的调用放入装饰函数中:

    from asgiref.sync import sync_to_async
    
    @sync_to_async
    def get_all_users():
        return User.objects.all()
    
    for user in await get_all_users():
        print(user)
    

    请注意,这必须在异步上下文中使用,因此完整示例如下所示:

    from asgiref.sync import sync_to_async
    
    @sync_to_async
    def get_all_users():
        return User.objects.all()
    
    async def foo(request):
        for user in await get_all_users():
            print(user)
    

    Full documentation

    【讨论】:

    • 返回此错误。 <ipython-input-8-ade7a982ccf1>:1: RuntimeWarning: coroutine 'SyncToAsync.__call__' was never awaited for user in get_all_users(): RuntimeWarning: Enable tracemalloc to get the object allocation traceback --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-8-ade7a982ccf1> in <module> ----> 1 for user in get_all_users(): 2 print(user) TypeError: 'coroutine' object is not iterable
    • 您的代码 (for user in ... 需要在异步函数中。您可能还需要等待它,因为您要将它变成协程,例如users = await get_all_users()
    • 但这不会导致整个代码异步,而不仅仅是foo 方法吗?这会增加代码的复杂性。
    【解决方案2】:

    发生错误是因为 Jupyter 笔记本有一个正在运行的事件循环。 来自documentation

    如果您尝试从正在运行事件循环的线程中运行任何这些部分,您将收到 SynchronousOnlyOperation 错误。请注意,您不必直接在异步函数内部就可以发生此错误。如果您直接从异步函数调用同步函数,而没有经过诸如 sync_to_async() 或线程池之类的操作,那么它也可能发生,因为您的代码仍在异步上下文中运行。

    如果您遇到此错误,您应该修复您的代码以不从异步上下文中调用有问题的代码;相反,编写您的代码在其自己的同步函数中与 async-unsafe 对话,并使用 asgiref.sync.sync_to_async() 或在其自己的线程中运行同步代码的任何其他首选方式调用它。

    如果您绝对迫切需要从异步上下文中运行此代码 - 例如,它是由外部环境强加给您的,并且您确定它不可能同时运行(例如,您是在 Jupyter 笔记本中),然后您可以使用 DJANGO_ALLOW_ASYNC_UNSAFE 环境变量禁用警告。

    由于问题是特定于 jupyter notebook 环境的,因此以下是一个有效的解决方案。

    import os
    import django
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rest.settings')
    os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
    django.setup()
        
    from users.models import User
    User.objects.all()
    

    但是,需要小心谨慎,不要根据文档警告在生产环境中使用它。

    【讨论】:

    • 这个答案比票数最高的要好得多。无需更改 Django src。
    • 简单,但正如文档中指出的那样,这可能会导致数据损坏:“如果您不小心尝试从异步视图调用仍然仅同步的 Django 的一部分,您将触发Django 的异步安全保护可保护您的数据免受损坏。”
    • 我只需要这些行(使用 3.2rc1 时,如果重要的话):import osos.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
    • 同意——这是让笔记本运行的最简单方法。毕竟,我们实际上并没有在这里运行服务,只是使用笔记本来浏览模型等。
    【解决方案3】:

    您不能在同步和异步函数之间传递查询集,因为它是惰性的。 您需要在异步上下文中对其进行评估。

    像这样:

    from django.contrib.auth.models import User
    from asgiref.sync import sync_to_async
    import asyncio
    
    
    @sync_to_async
    def get_users():
        return list(
            User.objects.all()
        )
    
    async def user_loop():
        results = await get_users()
        for r in results:
            print(r.username)
    
    
    loop = asyncio.get_event_loop()
    loop.run_until_complete(user_loop())
    

    【讨论】:

    • list() 为我成功了!谢谢
    【解决方案4】:

    来自documentation

    如果您尝试运行 Django 的任何非异步部分,例如 ORM,您将收到 SynchronousOnlyOperation 错误。请注意,您不必直接在异步函数中发生此错误。如果您直接从异步函数调用同步函数,而不使用 sync_to_async() 或类似函数,那么它也可能发生。这是因为您的代码仍在具有活动事件循环的线程中运行,即使它可能未声明为异步代码。

    你应该使用:

    from asgiref.sync import sync_to_async
    
    def _get_blog(pk):
        return Blog.objects.get(pk=pk)
    
    get_blog = await sync_to_async(_get_blog, thread_sensitive=True)(10)
    
    ....
    
    

    或者

    from asgiref.sync import sync_to_async
    
    get_blog = await sync_to_async(Blog.objects.get, thread_sensitive=True)(10)
    
    

    你可以使用 Django 频道的 database_sync_to_async,它是 asgiref.sync 的一个改进的 sync_to_async 函数

    例如:

    from channels.db import database_sync_to_async
    
    get_blog = await database_sync_to_async(Blog.objects.get, thread_sensitive=True)(10)
    
    

    【讨论】:

    • 我试过这个:get_blog = await sync_to_async(Blog.objects.all(), thread_sensitive=True) object SyncToAsync can't be used in 'await' expression
    • 如果要使用await,则必须在异步函数中使用database_sync_to_async或sync_to_async
    【解决方案5】:

    您可以从这里找到答案:https://docs.djangoproject.com/en/3.2/topics/async/#async-safety

    如果您尝试从正在运行事件循环的线程中运行任何这些部分,您将收到 SynchronousOnlyOperation 错误。请注意,您不必直接在异步函数中发生此错误。如果您直接从异步函数调用同步函数,而不使用 sync_to_async() 或类似函数,那么它也可能发生。这是因为您的代码仍在具有活动事件循环的线程中运行,即使它可能未声明为异步代码。

    【讨论】:

      猜你喜欢
      • 2021-12-23
      • 2022-11-27
      • 2023-03-03
      • 1970-01-01
      • 2023-01-27
      • 1970-01-01
      • 2014-08-11
      • 1970-01-01
      • 2020-12-20
      相关资源
      最近更新 更多