我有一个使用 Twisted Python IRC 协议(protocol)编写的 IRC 机器人。我希望能够在运行命令的同时仍然允许机器人同时监听和执行其他命令。
例如,假设我有将一个大文本文件打印到 channel 的命令。如果我想在命令运行时通过在 channel 中输入“!stop”来停止命令,我该如何实现呢?或者假设我想在一个 channel 中执行“!print largefile”,然后转到另一个 channel 并键入“!print anotherfile”并让它在完成第一个文件的打印之前将该文件打印到另一个 channel 。
我在想我会为此目的使用线程吗?我不太确定。
编辑(澄清):
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
如果我想同时在两个 channel 中运行 !printfile 或在运行时停止 printfile 命令,我应该怎么做?
请您参考如下方法:
您不能中断 printfile 命令的原因是它包含一个遍历文件全部内容的循环。这意味着 privmsg 函数将一直运行,直到它读取并发送了文件中的所有行。只有在完成该工作后,它才会返回。
Twisted 是一个单线程协作多任务系统。一次只能运行程序的一部分。在您的 irc bot 可以处理来自 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 服务器也可能会缓冲这些线路,并且只会缓慢地将它们全部释放到网络中。如果 bot 已经发送了这些行并且它们位于 irc 服务器的缓冲区中,您将无法通过告诉 bot 停止来阻止它们出现在网络上(因为它已经完成)。而且,正因为如此,Twisted 的 irc 客户端也有缓冲,所以即使在你调用 self.msg 之后,该行可能实际上被发送,因为 irc 客户端是缓冲线以避免发送它们太快以至于 irc 服务器将 bot 踢出网络。因为我写的代码只处理对 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))
在这里,我更改了迭代器,以便从中出来的元素是 deferLater 中的 Deferred(之前,元素都是 None,因为那是self.msg 的返回值)。
当 cooperate 遇到 Deferred 时,它将停止在该迭代器上工作,直到 Deferred 触发。以这种方式使用的deferLater 基本上是一个协作 sleep 函数。它返回一个 Deferred,直到 2 秒后才会触发(然后它以 None 触发,cooperate 并不特别关心关于)。在它触发后,cooperate 将继续在迭代器上工作。所以现在 _printfile 每两秒只发送一行,这将更容易用停止命令中断。
