我有一个IRC bot,它是我使用Twisted Python IRC协议编写的。我希望能够运行命令,同时仍然允许机器人同时监听和执行其他命令。
例如,假设我有一个命令,它将把一个大的文本文件打印到一个频道。如果我想在命令运行时通过在通道中输入"!stop"来停止命令,我该如何完成?或者,假设我想在一个通道中执行"!print largefile",然后转到另一个通道并键入"!print anotherfile",让它在打印完第一个文件之前将该文件打印到另一个渠道。
我在想我会使用线程来达到这个目的吗?我不太确定。
编辑(澄清):
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
如果我想同时在两个通道中运行!printfile
,或者在运行时停止printfile命令,我该怎么办?
不能中断printfile命令的原因是它包含一个覆盖文件全部内容的循环。这意味着privmsg
函数将一直运行,直到它读取并发送了文件中的所有行。只有在它完成那项工作之后,它才会回来。
Twisted是一个单线程协作多任务系统。一次只能运行程序的一部分。在irc服务器的下一行输入可以由irc机器人处理之前,privmsg
必须返回。
然而,Twisted也擅长处理事件和管理并发。因此,这个问题的一个解决方案是使用Twisted中包含的一个工具(而不是for循环)发送文件,Twisted是一个与系统其他部分协作并允许同时处理其他事件的工具。
这里有一个简短的例子(未经测试,有一些明显的问题(比如两个打印文件命令靠得太近时的糟糕行为),我不会在这里尝试解决):
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('rn')) 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))
在这里,我已经更改了迭代器,使其产生的元素是来自deferLater
的Deferred(以前,这些元素都是None
,因为这是self.msg
的返回值)。
当cooperate
遇到Deferred
时,它将停止对该迭代器的操作,直到Deferred
触发为止。以这种方式使用的CCD_ 14基本上是协作睡眠功能。它返回一个Deferred
,直到2秒后才会触发(然后它用None
触发,cooperate
并不特别关心)。在它触发之后,cooperate
将继续对迭代器进行操作。所以现在_printfile
每两秒钟只发送一行,这将更容易用停止命令来中断。