#python #django #asynchronous #django-channels
#питон #джанго #асинхронный #django-каналы
Вопрос:
Краткие сведения
Здравствуйте, в настоящее время я работаю над приложением, которое отправляет данные на интерфейс в режиме реального времени с помощью django-каналов. Я смог сделать это для всех моделей, которые создаются при открытии их страницы, однако я еще не смог захватить предыдущие модели, когда страница еще не была открыта.
Код
Ниже была одна из моих первых попыток заставить эту систему работать, это будет вызвано, когда будет запущен метод подключения потребителя, и после того, как соединение было принято, я бы запустил этот цикл for
for app in Application.objects.all():
app_json = ApplicationSerializer(app).data
await self.send_json({'action': 'create', 'data': app_json})
При запуске этого кода я получаю сообщение об ошибке
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
Это имеет смысл, поскольку метод подключения потребителей является асинхронным, поэтому, следуя совету сообщения, я пошел дальше и использовал sync_to_async
for app in sync_to_async(Application.objects.all)():
app_json = ApplicationSerializer(app).data
await self.send_json({'action': 'create', 'data': app_json})
Я решил использовать sync_to_async
объекты приложения, поскольку в сообщении об ошибке была выделена сама строка цикла for, а также я знаю, что applicationSerializer будет работать правильно, поскольку я использовал его в других асинхронных методах просто отлично
Это приводит к TypeError: 'coroutine' object is not iterable
Заключительные мысли
Я новичок как в django-каналах, так и в асинхронных, если бы я знал, что каналы будут в значительной степени основаны на асинхронных, я бы изучил систему до начала этого проекта.
Я просмотрел другие сообщения о переполнении стека и увидел, что потенциально отображение данных может быть решением, но я не уверен на 100%, является ли это асинхронной проблемой или с django-каналами
Какие еще вещи я могу попробовать?
Обновить
Основываясь на предложении Kens, я пошел дальше и попробовал следующий код:
apps = await database_sync_to_async(Application.objects.all)()
for app in apps:
app_json = ApplicationSerializer(app).data
print(app_json)
Это вернуло трассировку стека
Exception inside application: You cannot call this from an async context - use a thread or sync_to_async.
Traceback (most recent call last):
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsstaticfiles.py", line 44, in __call__
return await self.application(scope, receive, send)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsrouting.py", line 71, in __call__
return await application(scope, receive, send)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelssessions.py", line 47, in __call__
return await self.inner(dict(scope, cookies=cookies), receive, send)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelssessions.py", line 254, in __call__
return await self.inner(wrapper.scope, receive, wrapper.send)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsauth.py", line 181, in __call__
return await super().__call__(scope, receive, send)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsmiddleware.py", line 26, in __call__
return await self.inner(scope, receive, send)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsrouting.py", line 150, in __call__
return await application(
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsconsumer.py", line 94, in app
return await consumer(scope, receive, send)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsconsumer.py", line 58, in __call__
await await_many_dispatch(
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsutils.py", line 51, in await_many_dispatch
await dispatch(result)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsconsumer.py", line 73, in dispatch
await handler(message)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsgenericwebsocket.py", line 196, in websocket_receive
await self.receive(text_data=message["text"])
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packageschannelsgenericwebsocket.py", line 259, in receive
await self.receive_json(await self.decode_json(text_data), **kwargs)
File "D:HomeAutomationHomeAutomationconsumers.py", line 28, in receive_json
for app in apps:
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packagesdjangodbmodelsquery.py", line 287, in __iter__
self._fetch_all()
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packagesdjangodbmodelsquery.py", line 1308, in _fetch_all
self._result_cache = list(self._iterable_class(self))
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packagesdjangodbmodelsquery.py", line 53, in __iter__
results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packagesdjangodbmodelssqlcompiler.py", line 1154, in execute_sql
cursor = self.connection.cursor()
File "C:UsersDevin.virtualenvsHomeAutomation-l9uhKhE0libsite-packagesdjangoutilsasyncio.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.
Ответ №1:
Этот код sync_to_async(Application.objects.all)()
создает сопрограмму, которую необходимо ожидать, а не использовать напрямую. Для вашего конкретного случая было бы лучше сначала получить данные за пределами цикла for, а затем продолжить. Кроме того, вы можете захотеть использовать database_sync_to_async
вместо этого.
from channels.db import database_sync_to_async
apps = await database_sync_to_async(Application.objects.all)()
for app in apps:
app_json = ApplicationSerializer(app).data
await self.send_json({'action': 'create', 'data': app_json})
В качестве дополнительного примечания убедитесь, что при сериализации экземпляра приложения сериализатором не будет запросов к базе данных. Для этого используйте select_related
и prefetch_related
для предварительной выборки любых вложенных объектов, которые могут потребоваться
ОБНОВЛЕНИЕ: Да, из-за ленивой природы некоторых методов Django ORM, таких как .all()
, база данных на самом деле будет удалена не в момент вызова метода, а в момент его использования. Распространенный способ заставить его выполнить немедленно — преобразовать набор запросов в список. Попробуй это:
from channels.db import database_sync_to_async
apps = await database_sync_to_async(list)(Application.objects.all())
for app in apps:
app_json = ApplicationSerializer(app).data
await self.send_json({'action': 'create', 'data': app_json})
Комментарии:
1. Здравствуйте, я пошел дальше и попробовал то, что вы предложили, и, что интересно, я получил ту же ошибку
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
, что и там ошибка на этот раз возникает, когда мы объявляемapps
2. @Timberghost_ опубликуйте полную трассировку стека
3. Спасибо за быстрый ответ, я пошел дальше и обновил сообщение с помощью stacktrace
4. @Timberghost_ да, виноват. Проверьте мое редактирование
5. я рад, что смог быть вам полезен. Это не обязательно должен быть конкретный список, но это то, что обычно используется, потому что список похож на queryset, и вы все равно можете перебирать его. По соображениям производительности и, возможно, по другим причинам, Django не выполняет запрос, он доходит до точки, где необходимы объекты. Чтобы преобразовать набор запросов в список Python, ему необходимо загрузить данные из базы данных, следовательно, выполнить запрос. Вы можете прочитать об этом в документации docs.djangoproject.com/en/3.1/topics/db/queries /. …