Python asyncio-任务退出的循环退出已被破坏,但仍处于待处理 [英] Python asyncio - Loop exits with Task was destroyed but it is pending

查看:650
本文介绍了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.

推荐答案

这是因为不和谐的客户端模块每分钟左右需要控制一次。

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具有全局解释器锁),并使用discord bot作为其薄层。

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/lates t / 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个部分,不协调的交互和处理服务器:

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进程发送到另一个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天全站免登陆