Python asyncio - 循环退出任务已被销毁但它正在等待
Python asyncio - Loop exits with Task was destroyed but it is pending
这是我的python程序的相关代码:
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()
程序偶尔会退出(自行退出,但它不应该),除
外没有其他错误或警告
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+。
您必须在退出时手动停止您的任务:
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:
task = 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:
task.cancel()
try:
loop.run_until_complete(task)
except Exception:
pass
loop.close()
我认为 asyncio.sleep
期间不会出现问题。无论如何你不应该压制你得到的异常:
bot = Bot()
loop = asyncio.get_event_loop()
try:
# ...
except Exception as e:
loop.run_until_complete(bot.close())
raise e # <--- reraise exception you got while execution to see it (or log it here)
finally:
# ...
这是因为discord客户端模块每分钟左右需要控制一次
这意味着任何窃取控制权超过一定时间的函数都会导致 discord 的客户端进入无效状态(稍后将显示为异常,可能是在客户端的下一个方法调用时)。
要确保discord模块客户端能够ping通discord服务器,你应该使用真正的多线程解决方案。
一个解决方案是将所有繁重的处理卸载到一个单独的进程(一个单独的线程不会做,因为 Python 有一个全局解释器锁)并使用 discord bot 作为薄层,其职责是填充工作队列。
相关阅读:
https://discordpy.readthedocs.io/en/latest/faq.html#what-does-blocking-mean
示例解决方案...这远远超出了问题的范围,但我已经编写了大部分代码。如果我有更多时间,我会写一个更短的解决方案:)
2部分,discord交互及处理服务器:
这是不和谐的监听器。
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)
这是处理繁重请求的工作服务器。您可以使这部分同步或异步。
我选择使用一种称为 websocket 的魔法将数据从一个 python 进程发送到另一个进程。但是你可以使用任何你想要的东西。例如,您可以让一个脚本将文件写入目录,而另一个脚本可以读出文件并处理它们。
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 脚本中处理两个进程,并告诉您的机器人 "sleep for 100",它会愉快地休眠 100 秒!
asyncio 的功能就像一个临时工作队列,你可以通过 运行 将它们作为单独的 python 脚本来适当地将监听器与后端处理分开。
现在,无论您在 'server' 部分中的功能 运行 多长时间,客户端部分都将永远无法 ping discord 服务器。
图片上传失败,但是...无论如何,这是让机器人休眠并回复的方法...注意休眠是同步的。
http://i.imgur.com/N4ZPPbB.png
这是我的python程序的相关代码:
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()
程序偶尔会退出(自行退出,但它不应该),除
外没有其他错误或警告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+。
您必须在退出时手动停止您的任务:
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:
task = 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:
task.cancel()
try:
loop.run_until_complete(task)
except Exception:
pass
loop.close()
我认为 asyncio.sleep
期间不会出现问题。无论如何你不应该压制你得到的异常:
bot = Bot()
loop = asyncio.get_event_loop()
try:
# ...
except Exception as e:
loop.run_until_complete(bot.close())
raise e # <--- reraise exception you got while execution to see it (or log it here)
finally:
# ...
这是因为discord客户端模块每分钟左右需要控制一次
这意味着任何窃取控制权超过一定时间的函数都会导致 discord 的客户端进入无效状态(稍后将显示为异常,可能是在客户端的下一个方法调用时)。
要确保discord模块客户端能够ping通discord服务器,你应该使用真正的多线程解决方案。
一个解决方案是将所有繁重的处理卸载到一个单独的进程(一个单独的线程不会做,因为 Python 有一个全局解释器锁)并使用 discord bot 作为薄层,其职责是填充工作队列。
相关阅读: https://discordpy.readthedocs.io/en/latest/faq.html#what-does-blocking-mean
示例解决方案...这远远超出了问题的范围,但我已经编写了大部分代码。如果我有更多时间,我会写一个更短的解决方案:)
2部分,discord交互及处理服务器:
这是不和谐的监听器。
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)
这是处理繁重请求的工作服务器。您可以使这部分同步或异步。
我选择使用一种称为 websocket 的魔法将数据从一个 python 进程发送到另一个进程。但是你可以使用任何你想要的东西。例如,您可以让一个脚本将文件写入目录,而另一个脚本可以读出文件并处理它们。
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 脚本中处理两个进程,并告诉您的机器人 "sleep for 100",它会愉快地休眠 100 秒! asyncio 的功能就像一个临时工作队列,你可以通过 运行 将它们作为单独的 python 脚本来适当地将监听器与后端处理分开。
现在,无论您在 'server' 部分中的功能 运行 多长时间,客户端部分都将永远无法 ping discord 服务器。
图片上传失败,但是...无论如何,这是让机器人休眠并回复的方法...注意休眠是同步的。 http://i.imgur.com/N4ZPPbB.png