printfile コマンドを中断できない理由は、ファイルの内容全体に対するループが含まれているためです。つまり、privmsg
関数は、ファイルからすべての行を読み取って送信するまで実行されます。その仕事が終わって初めて戻ってきます。
Twisted は、シングル スレッドの協調マルチタスク システムです。一度に実行できるのは、プログラムの一部だけです。irc サーバーからの次の入力行が irc ボットによって処理される前に、privmsg
戻る必要があります。
ただし、Twisted はイベントの処理と並行性の管理にも優れています。したがって、この問題の解決策の 1 つは、(for ループの代わりに) Twisted に含まれるツールの 1 つを使用してファイルを送信することです。このツールは、システムの残りの部分と連携し、その間に他のイベントを処理できるようにするツールです。
以下に簡単な例を示します (テストされておらず、明らかな問題 (2 つの 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 からの新しいメッセージが処理されることを意味します。
ただし、考慮すべきもう 1 つの点は、通常、irc サーバーにはフラッド保護が含まれていることです。これは、多くの回線を非常に迅速に送信すると、ボットが切断される可能性があることを意味します。最良の場合でも、irc サーバーは回線をバッファリングし、それらをネットワーク全体にゆっくりと解放するだけです。ボットがすでに回線を送信しており、回線が irc サーバーのバッファーに残っている場合、ボットに停止するように指示しても、回線がネットワークに表示されないようにすることはできません (既に終了しているため)。さらに、このため、Twisted の irc クライアントにもバッファリング機能があるため、 を呼び出した後でも、実際にself.msg
は行が送信されない場合があります。これは、irc サーバーがボットをキックするほど速く送信されるのを避けるために、irc クライアントが行をバッファリングしているためです。ネットワークから。私が書いたコードはを呼び出してself.msg
も、すべての行がすでに irc クライアントのローカル バッファに入っている場合は、行の送信を実際に停止できない場合があります。
これらすべての問題に対する明らかな (おそらく理想的ではない) 解決策の 1 つは、 で使用されるイテレータを少し複雑にすることです。そこに_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
(以前は、要素は allでした。None
それが の戻り値だからですself.msg
)。
にcooperate
遭遇すると、それが発火 Deferred
するまでそのイテレータでの作業を停止します。このように使用されるのは、基本的に協調的なスリープ機能です。2 秒が経過するまで発火しないa を返します(その後、特に気にしない で発火します) 。発火後、イテレータの作業を再開します。そのため、2 秒ごとに 1 行だけを送信するようになりました。これは、停止コマンドで中断するのがはるかに簡単になります。Deferred
deferLater
Deferred
None
cooperate
cooperate
_printfile