Python asyncio - 循环退出任务被破坏但它正在等待 [英] Python asyncio - Loop exits with Task was destroyed but it is pending

查看:71
本文介绍了Python asyncio - 循环退出任务被破坏但它正在等待的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的python程序的相关代码:

This is the relevant code of my python program:

import discord
import asyncio

class Bot(discord.Client):
    def __init__(self):
        super().__init__()

    @asyncio.coroutine
    def my_background_task(self):
        yield from self.wait_until_ready()
        while not self.is_closed:
            yield from asyncio.sleep(3600*24) # <- This is line 76 where it fails
            doSomething()

bot = Bot()
loop = asyncio.get_event_loop()
try:
    loop.create_task(bot.my_background_task())
    loop.run_until_complete(bot.login('username', 'password'))
    loop.run_until_complete(bot.connect())
except Exception:
    loop.run_until_complete(bot.close())
finally:
    loop.close()

程序偶尔会退出(自行退出,但它不应该退出)除了

The program occasionally quits (on its own, while it should not) with no other errors or warning other than

Task was destroyed but it is pending!
task: <Task pending coro=<my_background_task() running at bin/discordBot.py:76> wait_for=<Future pending cb=[Task._wakeup()]>>

如何保证程序不会随意退出?我在 Xubuntu 15.10 上有 Python 3.4.3+.

How to ensure the program won't randomly quit? I have Python 3.4.3+ on Xubuntu 15.10.

推荐答案

这是因为discord客户端模块每分钟左右需要控制一次.

This is because the discord client module needs control once every minute or so.

这意味着任何窃取控制权超过一定时间的函数都会导致 Discord 的客户端进入无效状态(这将在稍后的某个时间表现为异常,可能在客户端的下一个方法调用时).

This means that any function that steals control for more than a certain time causes discord's client to enter an invalid state (which will manifest itself as an exception some point later, perhaps upon next method call of client).

为了保证discord模块客户端能ping通discord服务器,你应该使用真正的多线程解决方案.

To ensure that the discord module client can ping the discord server, you should use a true multi-threading solution.

一种解决方案是将所有繁重的处理转移到一个单独的进程(单独的线程不会这样做,因为 Python 具有全局解释器锁),并将不和谐机器人用作一个薄层,其职责是填充工作队列.

One solution is to offload all heavy processing onto a separate process (a separate thread will not do, because Python has a global interpreter lock) and use the discord bot as a thin layer whose responsibility is to populate work queues.

相关阅读:https://discordpy.readthedocs.io/en/latest/faq.html#what-does-blocking-mean

示例解决方案...这超出了问题的范围,但我已经编写了大部分代码.如果我有更多时间,我会写一个更短的解决方案:)

Example solution... this is WAY beyond the scope of the problem, but I already had the code mostly written. If I had more time, I would write a shorter solution :)

2部分,discord交互和处理服务器:

2 parts, discord interaction and processing server:

这是不和谐的监听器.

import discord
import re
import asyncio
import traceback

import websockets
import json

# Call a function on other server
async def call(methodName, *args, **kwargs):
    async with websockets.connect('ws://localhost:9001/meow') as websocket:
        payload = json.dumps( {"method":methodName, "args":args, "kwargs": kwargs})
        await websocket.send(payload)
        #...
        resp = await websocket.recv()
        #...
        return resp

client = discord.Client()
tok = open("token.dat").read()

@client.event
async def on_ready():
    print('Logged in as')
    print(client.user.name)
    print(client.user.id)
    print('------')

@client.event
async def on_error(event, *args, **kwargs):
    print("Error?")

@client.event
async def on_message(message):
    try:
        if message.author.id == client.user.id:
            return
        m = re.match("(w+) for (d+).*?", message.content)
        if m:
            g = m.groups(1)
            methodName = g[0]
            someNumber = int(g[1])
            response = await call(methodName, someNumber)
            if response:
                await client.send_message(message.channel, response[0:2000])
    except Exception as e:
        print (e)
        print (traceback.format_exc())

client.run(tok)

这是处理繁重请求的工作服务器.您可以使这部分同步或异步.

This is the worker server for processing heavy requests. You can make this part sync or async.

我选择使用一种称为 websocket 的魔法将数据从一个 python 进程发送到另一个.但是你可以使用任何你想要的东西.例如,您可以让一个脚本将文件写入目录,而另一个脚本可以读取文件并处理它们.

I chose to use some magic called a websocket to send data from one python process to another one. But you can use anything you want. You could make one script write files into a dir, and the other script could read the files out and process them, for example.

import tornado
import tornado.websocket
import tornado.httpserver
import json
import asyncio
import inspect
import time

class Handler:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def consume(self, text):
        return "You said {0} and I say hiya".format(text)

    async def sweeps(self, len):
        await asyncio.sleep(len)
        return "Slept for {0} seconds asynchronously!".format(len)

    def sleeps(self, len):
        time.sleep(len)
        return "Slept for {0} seconds synchronously!".format(len)


class MyService(Handler, tornado.websocket.WebSocketHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def stop(self):
        Handler.server.stop()

    def open(self):
        print("WebSocket opened")

    def on_message(self, message):
        print (message)
        j = json.loads(message)
        methodName = j["method"]
        args = j.get("args", ())

        method = getattr(self, methodName)
        if inspect.iscoroutinefunction(method):
            loop = asyncio.get_event_loop()
            task = loop.create_task(method(*args))
            task.add_done_callback( lambda res: self.write_message(res.result()))
            future = asyncio.ensure_future(task)

        elif method:
            resp = method(*args)
            self.write_message(resp)

    def on_close(self):
        print("WebSocket closed")

application = tornado.web.Application([
    (r'/meow', MyService),
])

if __name__ == "__main__":
    from tornado.platform.asyncio import AsyncIOMainLoop
    AsyncIOMainLoop().install()

    http_server = tornado.httpserver.HTTPServer(application)
    Handler.server = http_server
    http_server.listen(9001)

    asyncio.get_event_loop().run_forever()

现在,如果您在单独的 python 脚本中运行这两个进程,并告诉您的机器人睡眠 100 秒",它会愉快地睡眠 100 秒!asyncio 的东西用作临时工作队列,您可以通过将侦听器作为单独的 python 脚本运行来将它们与后端处理适当地分开.

Now, if you run both processes in separate python scripts, and tell your bot "sleep for 100", it will sleep for 100 seconds happily! The asyncio stuff functions as a make-shift work queue, and you can properly separate the listener from the backend processing by running them as separate python scripts.

现在,无论您的函数在服务器"部分运行多长时间,都将永远不会阻止客户端部分 ping 不和谐服务器.

Now, no matter how long your functions run in the 'server' part, the client part will never be prevented from pinging the discord server.

图片上传失败,但是......无论如何,这是告诉机器人睡眠和回复的方法......注意睡眠是同步的.http://i.imgur.com/N4ZPPbB.png

Image failed to upload, but... anyway, this is how to tell the bot to sleep and reply... note that the sleep is synchronous. http://i.imgur.com/N4ZPPbB.png

这篇关于Python asyncio - 循环退出任务被破坏但它正在等待的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆