Twisted Python IRC bot - 如何异步执行函数以便它不会阻塞机器人?

Posted

技术标签:

【中文标题】Twisted Python IRC bot - 如何异步执行函数以便它不会阻塞机器人?【英文标题】:Twisted Python IRC bot - how to execute a function asynchronously so that it doesn't block the bot? 【发布时间】:2019-02-08 13:43:01 【问题描述】:

我正在尝试编写一个 IRC 机器人,它在执行长时间(10 多秒)功能时继续正常工作。

我首先使用套接字编写机器人。当我调用一个“阻塞”函数(执行需要几秒钟的计算)时,机器人自然停止响应,并且在函数计算时没有记录聊天中发送的任何消息。

我做了一些谷歌搜索,看到很多人推荐使用 Twisted。

我实现了基本的 IRC 机器人,主要基于一些示例:

# twisted imports
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.python import log

# system imports
import time, sys, datetime

def a_long_function():
    time.sleep(180)
    print("finished")

class BotMain(irc.IRCClient):

    nickname = "testIRC_bot"

    def connectionMade(self):
        irc.IRCClient.connectionMade(self)

    def connectionLost(self, reason):
        irc.IRCClient.connectionLost(self, reason)

    # callbacks for events

    def signedOn(self):
        """Signed to server"""
        self.join(self.factory.channel)

    def joined(self, channel):
        """Joined channel"""

    def privmsg(self, user, channel, msg):
        """Received message"""
        user = user.split('!', 1)[0]

        if 'test' in msg.lower():
            print("timeout started")

            a_long_function()

            msg = "test finished"
            self.msg(channel, msg)

        if 'ping' in msg.lower():
            self.msg(channel, "pong")
            print("pong")

class BotMainFactory(protocol.ClientFactory):
    """A factory for BotMains """

    protocol = BotMain

    def __init__(self, channel, filename):
        self.channel = channel
        self.filename = filename

    def clientConnectionLost(self, connector, reason):
        """Try to reconnect on connection lost"""
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        print ("connection failed:", reason)
        reactor.stop()

if __name__ == '__main__':

    log.startLogging(sys.stdout)
    f = BotMainFactory("#test", "log.txt")
    reactor.connectTCP("irc.freenode.net", 6667, f)
    reactor.run()

这种方法肯定比我之前的套接字实现要好,因为现在 bot 在执行 a_long_function() 时仍然接收发送的消息。

但是,它仅在功能完成后“看到”这些消息。这意味着当我将消息记录到 txt 文件时,在执行 a_long_function() 时收到的所有消息都会收到与函数完成时相同的时间戳 - 而不是在它们实际发送到聊天室时.

此外,机器人在执行 long 函数时仍然无法发送任何消息。

有人可以指出我应该如何更改代码的正确方向,以便可以异步执行这个长函数,以便机器人在执行时仍然可以记录和回复消息?

提前致谢。

编辑: 我遇到了this 答案,这让我想到我可以将 deferLater 调用添加到我的 a_long_function 中,以将其拆分为更小的块(也就是说需要 1 秒才能执行) ,并让机器人在两者之间恢复正常操作,以回复并记录同时发送到 IRC 频道的任何消息。或者可能添加一个计时器来计算 a_long_function 已经运行了多长时间,如果它超过阈值,它会调用 deferLater 让机器人赶上缓冲消息。

这确实有点像 hack 的想法 - 有没有更优雅的解决方案?

【问题讨论】:

【参考方案1】:

不,没有更优雅的解决方案。除非您想使用线程,这可能看起来更优雅,但很容易导致程序不稳定。如果可以避免,请使用延期解决方案。

【讨论】:

【参考方案2】:

要异步调用函数,您应该使用 asyncio 包以及 async/await 或协程。请记住,调用 async/await 是 v3 实现,而不是 v2。

使用异步/等待:

#!/usr/bin/env python3
# countasync.py

import asyncio

async def count():
    print("One")
    await asyncio.sleep(1)
    print("Two")

async def main():
    await asyncio.gather(count(), count(), count())

if __name__ == "__main__":
    import time
    s = time.perf_counter()
    asyncio.run(main())
    elapsed = time.perf_counter() - s
    print(f"__file__ executed in elapsed:0.2f seconds.")

有一个非常好的教程,您可以阅读here,它使用 asyncio 进行了深入介绍。

希望有帮助!

【讨论】:

这与使用 Twisted 会神奇地使代码非阻塞的原始建议一样错误。 asyncio.sleepdeferLater 做同样的事情。 asyncio 或 Twisted 都没有神奇地使代码非阻塞。没有什么关于 async 或 await 或 coroutines 可以神奇地使代码成为非阻塞的。将代码重写为非阻塞是使其成为非阻塞的原因。

以上是关于Twisted Python IRC bot - 如何异步执行函数以便它不会阻塞机器人?的主要内容,如果未能解决你的问题,请参考以下文章

Twisted Python IRC bot - 如何异步执行函数以便它不会阻塞机器人?

Twisted IRC Bot 与本地主机的连接反复丢失

Irc-bot 使用扭曲与期刊印刷

Python/Twisted IRC 机器人日志记录问题

Python twisted irc - 登录时的服务器密码

Python Twisted IRC,发送颜色信息