简体   繁体   English

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

[英]Twisted Python IRC bot - how to execute a function asynchronously so that it doesn't block the bot?

I'm trying to write an IRC bot that continues to work normally while it executes a long (10+ seconds) function. 我正在尝试编写一个IRC机器人,该机器人在执行很长的时间(超过10秒)后仍可以正常工作。

I started by writing the bot using socket. 我首先使用套接字编写机器人。 When I called a 'blocking' function (computation that takes few seconds to execute), the bot naturally stopped responding and did not record any messages sent in chat while the function was computing. 当我调用“阻止”功能(执行过程需要几秒钟的计算)时,该机器人自然会停止响应,并且在该函数进行计算时不会记录聊天中发送的任何消息。

I did some googling and saw a lot of people recommend using Twisted. 我做了一些谷歌搜索,看到很多人推荐使用Twisted。

I implemented basic IRC bot, heavily based on some examples: 我主要根据一些示例实现了基本的IRC bot:

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

This approach is definitely better than my earlier socket implementation, because now the bot still receives the messages sent while it executes a_long_function() . 这种方法绝对比我之前的套接字实现更好,因为现在该机器人在执行a_long_function()时仍然可以接收发送的消息。

However, it only 'sees' these messages after the function is complete. 但是,仅在功能完成后才“看到”这些消息。 This means that when I was logging the messages to txt file, all messages received when a_long_function() was executing receive the same timestamp of when the function has finished - and not when they were actually sent in the chatroom. 这意味着当我将消息记录到txt文件中时,执行a_long_function()时收到的所有消息都将收到与函数完成时间相同的时间戳,而不是在聊天室中实际发送的时间戳。

Also, the bot still isn't able to send any messages while its executing the long function. 此外,该机器人在执行long函数时仍无法发送任何消息。

Could someone point me in the right direction of how I should go about changing the code so that this long function can be executed asynchronously, so that the bot can still log and reply to messages as it's executing? 有人可以向我指出更改代码的正确方向,以便可以异步执行此长函数,以便机器人仍可以在执行过程中记录并回复消息吗?

Thanks in advance. 提前致谢。

Edit: I came across this answer, which gave me an idea that I could add deferLater calls into my a_long_function to split it into smaller chunks (that say take 1s to execute), and have the bot resume normal operation in between to reply to and log any messages that were sent to the IRC channel in mean time. 编辑:我遇到了这个答案,这使我有了一个想法,我可以将deferLater调用添加到我的a_long_function中,以将其拆分为较小的块(即需要1s来执行),然后让bot恢复其间的正常操作以回复和记录在同一时间发送到IRC通道的所有消息。 Or perhaps add a timer that counts how long a_long_function has been running for, and if its longer than a threshold, it would call a deferLater to let the bot catch up on the buffered messages. 或者,也许添加一个计时器,以计算a_long_function已运行了多长时间,如果它长于阈值,它将调用deferLater以使该漫游器追上缓冲的消息。

This does seem like a bit of hack thought - is there a more elegant solution? 这似乎有点骇人听闻-有更好的解决方案吗?

No, there is not really a more elegant solution. 不,实际上没有更优雅的解决方案。 Unless you want to use threading, which might look more elegant but could easily lead to an unstable program. 除非您想使用线程,否则线程看起来更优雅,但很容易导致程序不稳定。 If you can avoid it, go with the deferral solution. 如果可以避免,请采用延迟解决方案。

To asynchronously call a function, you should use the asyncio package along with async/await, or coroutines. 要异步调用函数,应将asyncio包与async / await或协程一起使用。 Keep in mind that calling async/await is a v3 implementation, not v2. 请记住,调用async / await是v3的实现,而不是v2。

Using async/await: 使用异步/等待:

#!/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.")

There is a really good tutorial you can read here that goes over using asyncio, in depth. 您可以在这里阅读一个非常好的教程,它会深入使用asyncio。

Hope of help! 希望有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM