2016-03-10 54 views
7

這是我的Python程序的相關代碼:Python的ASYNCIO - 退出循環利用任務被摧毀,但它正在等待

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+。

回答

2

這是因爲不和諧客戶端模塊需要每分鐘左右控制一次。

這意味着任何竊取超過一定時間的控件的函數都會導致不和諧的客戶端進入無效狀態(稍後可能會在客戶端的下一個方法調用時顯示爲異常)。

爲了確保不一致模塊客戶端可以ping不和服務器,您應該使用真正的多線程解決方案。

一個解決方案是將所有繁重處理卸載到單獨的進程上(單獨的線程不會這樣做,因爲Python具有全局解釋器鎖定)並將不和諧bot用作負責填充工作隊列的薄層。

相關閱讀: https://discordpy.readthedocs.io/en/latest/faq.html#what-does-blocking-mean

例的解決方案......這是遠遠超出了問題的範圍,但我已經有大部分的代碼編寫。如果我有更多的時間,我會寫一個短的解決方案:)

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的功能是作爲一個轉換工作隊列,你可以通過將它們作爲單獨的python腳本運行來正確地將監聽器與後端處理分開。

現在,無論您的函數在'服務器'部分運行多久,客戶端部分都不會被阻止對不和服務器執行ping操作。

圖片未能上傳,但是...無論如何,這是如何告訴機器人睡覺並回復...注意睡眠是同步的。 http://i.imgur.com/N4ZPPbB.png

+0

謝謝你在不和諧運作中的這種見解。不幸的是,我不是線程和異步編程方面的專家。你能解釋一下這個「薄層」是什麼以及如何實現它? – shrx

+0

有很多方法,這個討論超出了這個問題的範圍。我會瀏覽我自己的個人代碼(這很糟糕...因爲我寫了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

是的,我會包括你的取消()程序,但它不能解決我的問題 - 程序意外退出。有人建議,心跳間隔可能是問題。它可能以及如何解決它? – 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