使 Python 松弛机器人异步

Making a Python slack bot asynchronous

我一直在尝试在 Slack 中制作一个机器人,即使它没有完成对早期命令的处理也能保持响应,这样它就可以去做一些需要一些时间的事情而不会锁定。它应该return先完成的。

我想我已经完成了一部分:它现在不会忽略在较早的命令完成之前输入的内容 运行。但它仍然不允许线程彼此 "overtake" - 一个被称为 first 的命令将首先 return,即使它需要更长的时间才能完成。

import asyncio
from slackclient import SlackClient
import time, datetime as dt

token = "my token"
sc = SlackClient(token)

@asyncio.coroutine
def sayHello(waitPeriod = 5):
    yield from asyncio.sleep(waitPeriod)
    msg = 'Hello! I waited {} seconds.'.format(waitPeriod)
    return msg

@asyncio.coroutine
def listen():
    yield from asyncio.sleep(1)
    x = sc.rtm_connect()
    info = sc.rtm_read()
    if len(info) == 1:
            if r'/hello' in info[0]['text']:
                print(info)
                try:
                    waitPeriod = int(info[0]['text'][6:])
                except:
                    print('Can not read a time period. Using 5 seconds.')
                    waitPeriod = 5
                msg = yield from sayHello(waitPeriod = waitPeriod)
                print(msg)
                chan = info[0]['channel']
                sc.rtm_send_message(chan, msg)      

    asyncio.async(listen())

def main():
    print('here we go')
    loop = asyncio.get_event_loop()
    asyncio.async(listen())
    loop.run_forever()

if __name__ == '__main__':
    main()

当我在 Slack 聊天 window 中键入 /hello 12/hello 2 时,机器人现在会响应这两个命令。但是,在完成 /hello 12 命令之前,它不会处理 /hello 2 命令。我对 asyncio 的理解还在进行中,所以我很可能犯了一个非常基本的错误。我在 中被告知 sc.rtm_read() 之类的东西是阻塞函数。这是我问题的根源吗?

非常感谢, 亚历克斯

发生的事情是您的 listen() 协程在 yield from sayHello() 语句处阻塞。只有 sayHello() 完成后,listen() 才能继续其愉快的旅程。症结在于 yield from 语句(或 Python 3.5+ 中的 await)正在阻塞。它将两个协程链接在一起,并且 'parent' 协程在链接的 'child' 协程完成之前无法完成。 (但是,'neighbouring' 不属于同一链接链的协程可以同时自由进行。

在这种情况下释放 sayHello() 而不阻止 listen() 的简单方法是使用 listen() 作为专用的侦听协程并将所有后续操作卸载到它们自己的 Task 包装器,因此不会妨碍 listen() 响应后续传入的消息。沿着这些路线。

@asyncio.coroutine
def sayHello(waitPeriod, sc, chan):
    yield from asyncio.sleep(waitPeriod)
    msg = 'Hello! I waited {} seconds.'.format(waitPeriod)
    print(msg)
    sc.rtm_send_message(chan, msg)   

@asyncio.coroutine
def listen():
    # connect once only if possible:
    x = sc.rtm_connect()
    # use a While True block instead of repeatedly calling a new Task at the end
    while True:
        yield from asyncio.sleep(0)  # use 0 unless you need to wait a full second?
        #x = sc.rtm_connect() # probably not necessary to reconnect each loop?
        info = sc.rtm_read()
        if len(info) == 1:
                if r'/hello' in info[0]['text']:
                    print(info)
                    try:
                        waitPeriod = int(info[0]['text'][6:])
                    except:
                        print('Can not read a time period. Using 5 seconds.')
                        waitPeriod = 5
                    chan = info[0]['channel']
                    asyncio.async(sayHello(waitPeriod, sc, chan))