【问题标题】:Getting SynchronousOnlyOperation error Even after using sync_to_async in django and discord.py即使在 django 和 discord.py 中使用 sync_to_async 后,也会出现 SynchronousOnlyOperation 错误
【发布时间】:2020-07-29 08:39:18
【问题描述】:

我正在尝试为 Django 网站制作一个不和谐的机器人。在实现的过程中发现Django数据库不允许异步操作,必须使用线程或者sync_to_async

我使用了sync_to_async,它似乎有效。但是后来我遇到了一个问题,我什至无法弄清楚它有什么问题以及那里发生了什么。

谁能解释我在那里做错了什么以及那里发生了什么?

@sync_to_async
def get_customer(id):
    try:
        return Customer.objects.get(discord_id=id)
    except:
        return None
#------

@bot.command()
async def categories(ctx):
    customer = await get_customer(ctx.author.id) #this line of code works well
    categories = await sync_to_async(Category.objects.all().filter)(customer=customer)
    #categories = await sync_to_async(Category.objects.filter)(customer=customer)#I tried this also, didn't work
    print(categories) #problem is in the line
    embed = Embed(title='', description="{}".format('\n'.join([ x.name for x in categories])))
    await ctx.send(embed=embed)

我得到的回溯错误是:

Running bot
Ignoring exception in command categories:
Traceback (most recent call last):
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 83, in wrapped
    ret = await coro(*args, **kwargs)
  File "discord_bot.py", line 76, in categories
    print(categories)
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/django/db/models/query.py", line 252, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/django/db/models/query.py", line 276, in __iter__
    self._fetch_all()
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/django/db/models/query.py", line 1261, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/django/db/models/query.py", line 57, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1150, in execute_sql
    cursor = self.connection.cursor()
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/django/utils/asyncio.py", line 24, in inner
    raise SynchronousOnlyOperation(message)
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/discord/ext/commands/bot.py", line 892, in invoke
    await ctx.command.invoke(ctx)
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 797, in invoke
    await injected(*ctx.args, **ctx.kwargs)
  File "/Users/rhidwan/Desktop/personal_transaction/venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 92, in wrapped
    raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

【问题讨论】:

  • 你导入ASGI了吗
  • @SachinYadav 我导入了该行,但是,这不是导入错误。导致之前的一些操作成功了。
  • 在 django 3.1 上遇到同样的问题

标签: python django python-asyncio discord.py django-orm


【解决方案1】:

如果您仍然需要有关问题的帮助;

似乎这个问题与 Django 中查询集的延迟评估有关。基本上,当你打电话时

MyModel.objects.filter(key=value) 

查询集不是立即评估的,它仅在您使用它时评估,这可能是对其进行迭代,将其转换为列表,或获取它的字符串表示形式。

在您的示例中,即使您使用 sync_to_async 包装您的 Django 调用,当您想要使用打印查询集时也会评估查询集

print(categories)

因为它是在异步上下文中,所以你会得到错误。为避免这种情况,您可以强制在 sync_to_async 中评估查询集,例如

@sync_to_async
def get_categories_value(customer):
    return list(Category.objects.all().filter(customer=customer))

...

categories = await get_categories_value()
print(categories)

【讨论】:

  • list() 帮了我大忙!谢谢
【解决方案2】:

使用sync_to_async 包装常规函数使其看起来是异步的。在引擎盖下,该函数将在多个线程中执行,因此可能会发生竞争条件。 Django ORM 有很多线程安全的代码(连接对象、游标等),因此在多线程环境中“按原样”使用它是危险的。

您看到的消息是为了警告您潜在的问题。 Here是源码中对应的装饰器:

def async_unsafe(message):
    """
    Decorator to mark functions as async-unsafe. Someone trying to access
    the function while in an async context will get an error message.
    """

在 Django 代码库中搜索 async_unsafe 会让您很好地了解 ORM 的哪些部分被认为是不安全的。

只需在项目的设置文件中将DJANGO_ALLOW_ASYNC_UNSAFE 环境变量设置为true 即可解决问题:

import os
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

但这很可能会在以后狠狠地咬你。不要这样做。

我相信这里唯一的选择是在没有 Django ORM 的情况下直接访问数据库。这样您就可以完全控制跨线程共享的对象。

【讨论】:

    【解决方案3】:

    我遇到了这个问题,我注意到当您尝试访问作为外键和 @sync_to_async 之外的另一个键的模型字段时会发生这种情况。

    您的模型的__str__ 可能正在尝试访问其中一个字段,因此当您尝试在@sync_to_async 之外打印时,您会收到此错误。

    您必须在 @sync_to_async 中读取这些字段的值,并将其作为元组与其他项目一起返回

    【讨论】:

    • 可以做,但要避免在答案中使用“也许”。大胆
    【解决方案4】:

    当您在模型对象上调用 print 时,Django ORM 在下面工作,为您提供模型的字符串表示,并且 ORM 需要在同步模式下运行。因此,在这种情况下,您需要使用 sync_to_async 包装器调用 print。

    await sync_to_async(print)(categories)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-14
      • 2019-06-19
      • 2018-12-23
      • 1970-01-01
      • 1970-01-01
      • 2021-01-01
      • 1970-01-01
      • 2021-01-02
      相关资源
      最近更新 更多