繁体   English   中英

websocket 运行异步函数但返回错误:无法从正在运行的事件循环中调用 asyncio.run()

[英]websocket run async function but returns error: asyncio.run() cannot be called from a running event loop

我正在尝试使用 django-channels 2 来创建 websocket。 我需要运行一个异步方法,该方法应该返回命令的输出,以便我可以将数据传递回我网站上的用户。 我的问题是它不会让我运行它并出现错误:

asyncio.run() cannot be called from a running event loop

我做错了什么,我能做些什么?

消费者.py

class ChatConsumer(AsyncConsumer):

    async def websocket_connect(self, event):
        await self.send({
            "type": "websocket.accept"
        })

        user = self.scope['user']
        get_task_id = self.scope['url_route']['kwargs']['task_id']

        await asyncio.run(self.run("golemcli tasks show {}".format(get_task_id)))

        await self.send({
            "type": "websocket.send",
            "text": "hey"
        })
    async def websocket_receive(self, event):
        print("receive", event)

    async def websocket_disconnect(self, event):
        print("disconnected", event)



    async def run(self, cmd):
        proc = await asyncio.create_subprocess_shell(
        cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

        stdout, stderr = await proc.communicate()

        print(f'[{cmd!r} exited with {proc.returncode}]')
        if stdout:
            print(f'[stdout]\n{stdout.decode()}')
        if stderr:
            print(f'[stderr]\n{stderr.decode()}')

追溯:

Exception inside application: asyncio.run() cannot be called from a running event loop
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/sessions.py", line 183, in __call__
    return await self.inner(receive, self.send)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/middleware.py", line 41, in coroutine_call
    await inner_instance(receive, send)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/consumer.py", line 62, in __call__
    await await_many_dispatch([receive], self.dispatch)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/utils.py", line 52, in await_many_dispatch
    await dispatch(result)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/consumer.py", line 73, in dispatch
    await handler(message)
  File "/Users/golemgrid/Documents/GitHub/GolemGrid/overview/consumers.py", line 19, in websocket_connect
    await asyncio.run(self.run("golemcli tasks show {}".format(get_task_id)))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/runners.py", line 34, in run
    "asyncio.run() cannot be called from a running event loop")
  asyncio.run() cannot be called from a running event loop

更新 2使用以下代码段时:

await self.run("golemcli tasks show {}".format(get_task_id)

它返回以下回溯:

Exception inside application: Cannot add child handler, the child watcher does not have a loop attached
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/sessions.py", line 183, in __call__
    return await self.inner(receive, self.send)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/middleware.py", line 41, in coroutine_call
    await inner_instance(receive, send)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/consumer.py", line 62, in __call__
    await await_many_dispatch([receive], self.dispatch)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/utils.py", line 52, in await_many_dispatch
    await dispatch(result)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/channels/consumer.py", line 73, in dispatch
    await handler(message)
  File "/Users/golemgrid/Documents/GitHub/GolemGrid/overview/consumers.py", line 19, in websocket_connect
    await self.run("golemcli tasks show {}".format(get_task_id))
  File "/Users/golemgrid/Documents/GitHub/GolemGrid/overview/consumers.py", line 37, in run
    stderr=asyncio.subprocess.PIPE)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/subprocess.py", line 202, in create_subprocess_shell
    stderr=stderr, **kwds)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py", line 1503, in subprocess_shell
    protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/unix_events.py", line 193, in _make_subprocess_transport
    self._child_watcher_callback, transp)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/unix_events.py", line 924, in add_child_handler
    "Cannot add child handler, "
  Cannot add child handler, the child watcher does not have a loop attached

您已经处于异步运行循环中,因此您可以根据自己的意愿进行操作。

处理消费者运行循环中的run调用

(由于bug需要 python 3.8)

await self.run("golemcli tasks show {}".format(get_task_id))不需要嵌套的 runloop。

这意味着如果在subprocess执行其工作时有任何新消息到达消费者,它们将排队等候并且在subprocess完成之前不会被执行。 (这是最简单的解决方案)。

这意味着您的hey消息将在run方法完成后发送。

创建一个嵌套的 runloop

(由于bug需要 python 3.8)

如果您希望您的消费者能够在您的subprocess处理新消息(发送给它)。 (这要复杂得多,除非您的 websocket 连接需要此功能,否则您不应该这样做)。

    async def websocket_connect(self, event):
        ...

        # start the run
        self.run_task = asyncio.create_task(self.run(...))        

        ...

    async def websocket_receive(self, event):
        print("receive", event)

    async def websocket_disconnect(self, event):
        print("disconnected", event)

        if not self.run_task.done():
            # Clean up the task for the queue we created
            self.run_task.cancel()
            try:
                # let us get any exceptions from the nested loop
                await self.run_task
            except CancelledError:
                # Ignore this error as we just triggered it
                pass
        else:
            # throw any error from this nested loop
            self.run_task.result()

作为阻塞任务运行但在线程池中

将运行更改为同步任务。

    # 2. Run in a custom thread pool:
    loop = asyncio.get_running_loop()
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(
            pool, self.run, "your cmd")
        print('custom thread pool', result)

作为单独的安全说明,您的代码很容易让某人在您的服务器上运行 bash 命令,因为您从 url 中的任务 id 获取原始字符串。 我建议在此处添加一些验证,如果您的数据库中有一个任务条目,则可能使用 url 值查找数据库中的任务,然后在构建命令时使用记录中的 id 值,或者如果这至少是不可能的确保 task_id 具有非常严格的正则表达式验证,以确保它不能包含您不期望的任何字符。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM