2016-03-10 5 views
7

Это соответствующий код моего питона программы:Python asyncio - Loop выходит с Task была разрушена, но она находится на рассмотрении

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()]>> 

Как обеспечить, чтобы программа не случайно удалялась? У меня Python 3.4.3+ на Xubuntu 15.10.

ответ

2

Это связано с тем, что клиентский модуль discord нуждается в контроле один раз в минуту или около того.

Это означает, что любая функция, которая крадет контроль более чем на определенное время, заставляет клиента разборки вводить недопустимое состояние (которое будет проявляться как исключение через некоторое время, возможно, при следующем вызове метода клиентом).

Для обеспечения того, чтобы клиент модуля discord мог выполнить ping-сервер разборки, вы должны использовать истинное многопоточное решение.

Одним из решений является разгрузка всей тяжелой обработки на отдельный процесс (отдельный поток не будет выполняться, поскольку Python имеет глобальную блокировку интерпретатора) и использовать бот-дисконт как тонкий слой, задачей которого является заполнение рабочих очередей.

Связанные чтения: https://discordpy.readthedocs.io/en/latest/faq.html#what-does-blocking-mean

Пример решения ... это WAY выходит за рамки этой проблемы, но я уже код в основном написан. Если бы у меня было больше времени, я бы написать короче решение :)

2 частей, разногласие взаимодействие и сервер обработки:

Это диссонанс слушателя.

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, и сказать бот «сон на 100», он будет спать в течение 100 секунд счастливо! Функция asyncio функционирует как рабочая очередь make-shift, и вы можете должным образом отделить прослушиватель от внутренней обработки, запустив их в виде отдельных скриптов python.

Теперь, независимо от того, как долго ваши функции выполняются в части «сервер», клиентская часть никогда не будет предотвращена для проверки сервера раздора.

Изображение не удалось загрузить, но ... так или иначе, это как сказать боту спать и ответить ... обратите внимание, что сон синхронный. http://i.imgur.com/N4ZPPbB.png

+0

Благодарим вас за понимание в работе раздора. К сожалению, я не эксперт в потоковом и асинхронном программировании. Не могли бы вы объяснить немного больше, что такое «тонкий слой» и как его реализовать? – shrx

+0

Существует много способов, и обсуждение выходит за рамки этого вопроса imo. Я просмотрю свой личный код (это довольно плохо ... потому что я написал ot) и посмотрю, могу ли я извлечь некоторые фрагменты и идеи для вас, хотя :) – JamEnergy

+0

Благодарим вас за подробное объяснение с примером. – shrx

0

Вы должны вручную остановить задание на выходе:

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() 
+0

Моя программа не должна выйти, она должна работать неограниченно и запускать функцию doSomething() один раз в день (между прочим). – shrx

+0

Но ваша программа определенно не завершает фоновое задание изящно, что создает предупреждающий текст при закрытии eventloop. Я рекомендую отмену фоновой задачи, чтобы предотвратить ее. –

+0

Да, я включу вашу процедуру cancel(), однако она не решит мою проблему - программа неожиданно завершает работу. Мне было предложено, чтобы интервалы пульса могли быть проблемой. Возможно ли и как его решить? – shrx

1

Я не думаю, что проблема происходит в то время как 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: 
    # ... 
+1

Хотя это и отвечает на вопрос, я подозреваю, что проблема на самом деле не задана должным образом в вопросе. Я не хочу заходить слишком далеко, вкладывая слова в рот щенка, но я думаю, что действительно спрашивали: «Как обеспечить, чтобы программа не случайно перестала?» без ограничения объема вопроса на сон как таковой. – JamEnergy

+0

@JamEnergy, вы правы, я отредактировал вопрос. – shrx