2

I have an IRC bot that I wrote using the Twisted Python IRC protocols. I want to be able to run commands while still allowing the bot to listen and execute other commands simultaneously.

For example, let's say I have command that will print a large text file to a channel. If I wanted to stop the command while it was running by entering "!stop" into the channel, how could I accomplish this? Or let's say I want to do "!print largefile" in one channel and then go to a different channel and type "!print anotherfile" and have it print that file to the other channel before even finishing printing the first file.

I'm thinking that I would use threading for this purpose? I'm not quite sure.

EDIT (to clarify):

def privmsg(self, user, channel, msg):
    nick = user.split('!', 1)[0]
    parts = msg.split(' ')
    trigger = parts[0]
    data = parts[1:]
    if trigger == '!printfile':
        myfile = open('/files/%s' % data[0], 'r')
        for line in myfile:
            line = line.strip('/r/n')
            self.msg(channel, line)
    if trigger == '!stop':
        CODE TO STOP THE CURRENTLY RUNNING PRINTFILE COMMAND

If I wanted to run !printfile in two channels at once or stop the printfile command while it is running, what should I do?

4

1 回答 1

1

您不能中断您的 printfile 命令的原因是它包含文件全部内容的循环。这意味着该privmsg函数将一直运行,直到它从文件中读取并发送了所有行。只有完成这项工作后,它才会返回。

Twisted 是一个单线程协作多任务系统。一次只能运行程序的一部分。在您的 irc 机器人可以处理来自 irc 服务器的下一行输入之前,privmsg必须返回。

然而,Twisted 也擅长处理事件和管理并发。因此,解决此问题的一种方法是使用 Twisted 中包含的工具之一(而不是 for 循环)发送文件 - 该工具与系统的其余部分协作并允许同时处理其他事件。

这是一个简短的示例(未经测试,并且存在一些明显的问题(例如当两个 printfile 命令靠得太近时的不良行为),我不会在此处尝试修复):

from twisted.internet.task import cooperate

....

def privmsg(self, user, channel, msg):
    nick = user.split('!', 1)[0]
    parts = msg.split(' ')
    trigger = parts[0]
    data = parts[1:]
    if trigger == '!printfile':
        self._printfile(channel, data[0])
    if trigger == '!stop':
        self._stopprintfile()

def _printfile(self, channel, name):
    myfile = open('/files/%s' % (name,), 'r')
    self._printfiletask = cooperate(
        self.msg(channel, line.rstrip('\r\n')) for line in myfile)


def _stopprintfile(self):
    self._printfiletask.stop()

这使用twisted.internet.task.cooperate了一个辅助函数,它接受一个迭代器(包括生成器)并以与应用程序的其余部分协作的方式运行它们。它通过迭代迭代器几次,然后让其他工作运行,然后返回到迭代器,依此类推,直到迭代器耗尽。

这意味着即使在发送文件时也会处理来自 irc 的新消息。

但是,要考虑的另一点是 irc 服务器通常包含洪水保护,这意味着非常快速地向它们发送许多线路可能会使您的机器人断开连接。即使在最好的情况下,irc 服务器也可能会缓冲这些行,并且只会将它们缓慢地释放到整个网络中。如果机器人已经发送了线路并且它们位于 irc 服务器的缓冲区中,您将无法通过告诉机器人停止来阻止它们出现在网络上(因为它已经完成了)。此外,正因为如此,Twisted 的 irc 客户端也有缓冲功能,所以即使在你调用 之后self.msg,该行也可能不会真正发送,因为 irc 客户端正在缓冲行,以避免发送它们太快以至于 irc 服务器踢机器人断网。因为我写的代码只处理调用self.msg如果它们都已经进入 irc 客户端的本地缓冲区,您实际上可能仍然无法停止发送行。

所有这些问题的一个明显(也许不是理想的)解决方案是_printfile通过在其中插入一个新的延迟来稍微复杂化使用的迭代器:

from twisted.internet import reactor
from twisted.internet.task import deferLater

def _printfileiterator(self, channel, myfile):
    for line in myfile:
        self.msg(channel, line)
        yield deferLater(reactor, 2, lambda: None)

def _printfile(self, channel, name):
    myfile = open('/files/%s' % (name,), 'r')
    self._printfiletask = cooperate(self._printfileiterator(channel, myfile))

在这里,我更改了迭代器,以便从中出来的元素是 Deferreds from deferLater(以前,元素都是None,因为这是 的返回值self.msg)。

cooperate遇到 aDeferred时,它会停止在该迭代器上工作,直到Deferred触发之后。 deferLater这种方式使用基本上是协同睡眠功能。它返回 aDeferred直到 2 秒后才会触发(然后它会触发None,这cooperate并不特别关心)。触发后,cooperate将继续在迭代器上工作。所以现在_printfile每两秒只发送一条线,使用停止命令更容易中断。

于 2012-08-02T11:23:22.383 回答