You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django Channels中@database_sync_to_async函数依赖print语句才能运行的问题

Django Channels异步查询异常:移除print语句触发同步操作错误

问题说明

用Django Channels开发功能时,遇到一个异常问题:被@database_sync_to_async装饰的get_driver_location函数,必须保留print(location)语句才能正常运行;一旦注释掉该print语句,就会抛出错误:You cannot call this from an async context - use a thread or sync_to_async.

相关代码

@database_sync_to_async
def get_driver_data(self, data):
    driver = DriverAdditional.objects.get(user_id=data)
    return driver

@database_sync_to_async
def get_driver_location(self, data):
    location = CabLocation.objects.get(driver_id=data)
    # print(location)  # 注释掉这行就触发报错
    return location

async def send_amount(self, text_data):
    print("send amount")
    user = self.scope['user'].id
    print("user", user)
    driver = await self.get_driver_data(user)
    message = json.loads(text_data)
    data = message.get('data')
    try:
        location = await self.get_driver_location(driver)
        print(location)
    except Exception as e:
        print(f'error is {e}')
    await self.channel_layer.group_send(
        group=data['rider'],
        message={
            'type': 'echo.message',
            'message_type': 'send_amount',
            'data': f"{driver, location} {data['amount']}",
        }
    )

移除print语句后的报错信息

Exception inside application: You cannot call this from an async context - use a thread or sync_to_async.
Traceback (most recent call last):
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\fields\related_descriptors.py", line 218, in __get__
rel_obj = self.field.get_cached_value(instance)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\fields\mixins.py", line 15, in get_cached_value
return instance._state.fields_cache[cache_name]
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
KeyError: 'driver_id'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "D:\Najam\June\development\backend\Lib\site-packages\django\contrib\staticfiles\handlers.py", line 101, in __call__
return await self.application(scope, receive, send)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\routing.py", line 62, in __call__
return await application(scope, receive, send)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\sessions.py", line 47, in __call__
return await self.inner(dict(scope, cookies=cookies), receive, send)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\sessions.py", line 263, in __call__
return await self.inner(wrapper.scope, receive, wrapper.send)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\auth.py", line 185, in __call__
return await super().__call__(scope, receive, send)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\middleware.py", line 24, in __call__
return await self.inner(scope, receive, send)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\routing.py", line 116, in __call__
return await application(
         ^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\consumer.py", line 94, in app
return await consumer(scope, receive, send)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\channels\consumer.py", line 58, in __call__
await await_many_dispatch(
File "D:\Najam\June\development\backend\Lib\site-packages\channels\utils.py", line 50, in await_many_dispatch
await dispatch(result)
File "D:\Najam\June\development\backend\Lib\site-packages\channels\consumer.py", line 73, in dispatch
await handler(message)
File "D:\Najam\June\development\backend\Lib\site-packages\channels\generic\websocket.py", line 194, in websocket_receive
await self.receive(text_data=message["text"])
File "D:\Najam\June\development\baba_drive_project_backend\app\rides\consumers.py", line 399, in receive
await self.send_amount(text_data)
File "D:\Najam\June\development\baba_drive_project_backend\app\rides\consumers.py", line 443, in send_amount
    'data': f"{driver,location} {data['amount']}",
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\base.py", line 588, in __repr__
return "<%s: %s>" % (self.__class__.__name__, self)
                                                  ^^^^
File "D:\Najam\June\development\baba_drive_project_backend\app\location\models.py", line 29, in __str__
return f'{self.driver_id.name}'
               ^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\fields\related_descriptors.py", line 236, in __get__
rel_obj = self.get_object(instance)
              ^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\fields\related_descriptors.py", line 366, in get_object
return super().get_object(instance)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\fields\related_descriptors.py", line 199, in get_object
return qs.get(self.field.get_reverse_related_filter(instance))
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\query.py", line 633, in get
num = len(clone)
           ^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\query.py", line 380, in __len__
self._fetch_all()
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\query.py", line 1881, in _fetch_all
self._result_cache = list(self._iterable_class(self))
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\query.py", line 91, in __iter__
results = compiler.execute_sql(
              ^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\site-packages\django\db\models\sql\compiler.py", line 1560, in execute_sql
cursor = self.connection.cursor()
             ^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Najam\June\development\backend\Lib\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.

原因分析

从报错栈可以定位到问题根源:
在group_send的data字段格式化时,f"{driver,location} {data['amount']}"会调用location实例的__str__方法。而CabLocation模型的__str__方法里访问了self.driver_id.name,这会触发Django的懒加载机制——也就是在异步上下文里执行了同步的数据库查询,直接违反了Django的异步操作规则。

添加print(location)时,print会在@database_sync_to_async装饰的同步线程中调用location的__str__,此时懒加载的数据库查询是合法的,并且会把driver_id的数据缓存到实例中。后续在异步上下文里格式化字符串时,直接使用缓存值,不会触发新的查询,因此不会报错。

解决方案

1. 预加载关联对象(推荐)

修改get_driver_location函数,用select_related提前加载关联的driver_id数据,避免后续懒加载:

@database_sync_to_async
def get_driver_location(self, data):
    # 预加载关联的driver_id数据,避免后续异步环境中触发查询
    location = CabLocation.objects.select_related('driver_id').get(driver_id=data)
    return location

2. 在同步线程中处理字符串格式化

提前在同步函数中生成需要的字符串,避免在异步上下文里调用模型的__str__:

@database_sync_to_async
def get_driver_location(self, data):
    location = CabLocation.objects.get(driver_id=data)
    # 提前生成格式化后的字符串
    return (location, f"{location}")

async def send_amount(self, text_data):
    # ... 其他代码 ...
    try:
        location, location_str = await self.get_driver_location(driver)
    except Exception as e:
        print(f'error is {e}')
    await self.channel_layer.group_send(
        group=data['rider'],
        message={
            'type': 'echo.message',
            'message_type': 'send_amount',
            'data': f"{driver} {location_str} {data['amount']}",
        }
    )

3. 修改模型__str__方法(临时方案)

修改CabLocation的__str__,避免触发懒加载,但会丢失部分信息,仅作临时应急:

class CabLocation(models.Model):
    # ... 其他字段定义 ...
    def __str__(self):
        try:
            return f'{self.driver_id.name}'
        except:
            # 无法获取关联数据时返回基础标识
            return f'CabLocation {self.pk}'

内容的提问来源于stack exchange,提问作者najam iqbal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 12:59:49