RuntimeError:超时上下文管理器应该在任务中使用

Sup*_*oks 13 python flask python-asyncio discord.py

背景:我在 Discord 客户端旁边托管了一个 Flask 服务器

Flask 服务器只需要将来自客户端的消息传递给 discord,并将来自 discord 的消息传递给客户端。

我得到的错误,当我打电话loop.run_until_complete(sendMsg(request)) 我试图wait_forsendMsgwait_for loop.run_until_complete()

我到处找,没有找到任何东西,所以任何帮助将不胜感激。

代码:

import discord
import json
import os
import asyncio
from flask import Flask, request, render_template
from async_timeout import timeout
from threading import Thread
from time import sleep

client = discord.Client()
messages = []
app = Flask(__name__)

def startClient():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    client.run('token')


#
# Discord Events
#
@client.event
async def on_ready():
    print('Discord Client Ready')

@client.event
async def on_message(message):
    global messages
    message.append(message)


#
# Flask Stuff
#
async def sendMsg(request):
    await client.send_message(discord.Object('channel id'), request.form['message'])


@app.route("/chat/", methods=['GET', 'POST'])
def chatPage():
    global messages

    if request.method == 'GET':
        return render_template('main.html')

    elif request.method == 'POST':
        loop = asyncio.new_event_loop()
        loop.run_until_complete(sendMsg(request))
        return ''

@app.route("/chat/get", methods=['GET'])
def chatGet():
    return json.dumps(messages[int(request.args['lastMessageId']):])


# Start everything
os.environ["WERKZEUG_RUN_MAIN"] = 'true'
print('Starting discord.py client')
Thread(target=startClient).start()
print('Starting flask')
app.run(host='0.0.0.0', debug=True)
Run Code Online (Sandbox Code Playgroud)

追溯:

Traceback (most recent call last):
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/mnt/c/Users/SuperKooks/Documents/Coding/HTML/kindle-discord/app.py", line 51, in chatPage
    loop.run_until_complete(sendMsg(request))
  File "/usr/lib/python3.5/asyncio/base_events.py", line 387, in run_until_complete
    return future.result()
  File "/usr/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/mnt/c/Users/SuperKooks/Documents/Coding/HTML/kindle-discord/app.py", line 39, in sendMsg
    await client.send_message(discord.Object('382416348007104513'), request.form['message'])
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/discord/client.py", line 1152, in send_message
    data = yield from self.http.send_message(channel_id, content, guild_id=guild_id, tts=tts, embed=embed)
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/discord/http.py", line 137, in request
    r = yield from self.session.request(method, url, **kwargs)
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/aiohttp/client.py", line 555, in __iter__
    resp = yield from self._coro
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/aiohttp/client.py", line 197, in _request
    with Timeout(timeout, loop=self._loop):
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/async_timeout/__init__.py", line 39, in __enter__
    return self._do_enter()
  File "/home/SuperKooks/.local/lib/python3.5/site-packages/async_timeout/__init__.py", line 76, in _do_enter
    raise RuntimeError('Timeout context manager should be used '
RuntimeError: Timeout context manager should be used inside a task
Run Code Online (Sandbox Code Playgroud)

kol*_*pto 14

aiohttp.ClientSession()想要在协程内被调用。尝试将您的Client()初始化程序移动到任何async def函数中

  • 这正是我的问题,尽管我不确定它是否回答了上面的问题。 (4认同)

use*_*342 9

问题看起来可能是由以下原因引起的:

elif request.method == 'POST':
    loop = asyncio.new_event_loop()
    loop.run_until_complete(sendMsg(request))
Run Code Online (Sandbox Code Playgroud)

这会创建一个新的事件循环并sendMsg(request)在新循环中运行。但是,在其自己的事件循环中运行sendMsgclient对象上调用方法。sendMsg应该提交到在另一个线程中运行客户端的现有事件循环。要做到这一点,您需要:

  • 将在 中创建的循环暴露startClientclient_loop全局变量;
  • 替换loop = asyncio.new_event_loop(); loop.run_until_complete(sendMsg(request))为调用以asyncio.run_coroutine_threadsafe将协程提交到已在不同线程中运行的事件循环。

提交代码如下所示:

elif request.method == 'POST':
    # submit the coroutine to the event loop thread
    send_fut = asyncio.run_coroutine_threadsafe(sendMsg(request), client_loop)
    # wait for the coroutine to finish
    send_fut.result()
Run Code Online (Sandbox Code Playgroud)

  • 有没有修复?我有这个确切的问题,这让我发疯 (2认同)
  • @JoshMenzel 修复在哪里?我相信问题出在 OP 的代码中,没有进一步的跟进。 (2认同)

JBS*_*rro 8

我有一种经过生产强化的技术来防止这两个错误:

  • RuntimeError: Timeout context manager should be used inside a task,
  • RuntimeError: This event loop is already running

这个想法是有一个专门的后台线程来播放事件循环,就像在传统的 UI 设置中一样,但是这里它不是用于 UI 消息,而是用于 API 消息(即请求)。我们启动该线程一次(在导入模块时,但实际上在任何地方)。此外,我们只调用asyncio_run代替asyncio.runasyncio_gather代替asyncio.gather

干得好:

import asyncio
import threading
from typing import Awaitable, TypeVar
T = TypeVar("T")

def _start_background_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()

_LOOP = asyncio.new_event_loop()
_LOOP_THREAD = threading.Thread(
    target=_start_background_loop, args=(_LOOP,), daemon=True
)
_LOOP_THREAD.start()

def asyncio_run(coro: Awaitable[T], timeout=30) -> T:
    """
    Runs the coroutine in an event loop running on a background thread,
    and blocks the current thread until it returns a result.
    This plays well with gevent, since it can yield on the Future result call.

    :param coro: A coroutine, typically an async method
    :param timeout: How many seconds we should wait for a result before raising an error
    """
    return asyncio.run_coroutine_threadsafe(coro, _LOOP).result(timeout=timeout)


def asyncio_gather(*futures, return_exceptions=False) -> list:
    """
    A version of asyncio.gather that runs on the internal event loop
    """
    async def gather():
        return await asyncio.gather(*futures, return_exceptions=return_exceptions)

    return asyncio.run_coroutine_threadsafe(gather(), loop=_LOOP).result()
Run Code Online (Sandbox Code Playgroud)


JBS*_*rro 7

我通过将所有调用替换为以下asyncio.run内容来修复此问题asyncio_run。它为我解决了这两个错误:

  • RuntimeError: Timeout context manager should be used inside a task
  • RuntimeError: This event loop is already running
pip install nest-asyncio
Run Code Online (Sandbox Code Playgroud)
pip install nest-asyncio
Run Code Online (Sandbox Code Playgroud)

第二个目标是能够从 JS 世界或.NET 世界进行asyncio.run思考。promise.resolveTask.Wait