Twisted/Python - 从另一个线程调用协议内的方法
Posted
技术标签:
【中文标题】Twisted/Python - 从另一个线程调用协议内的方法【英文标题】:Twisted/Python - Call Method Inside a Protocol From Another Thread 【发布时间】:2012-03-30 18:54:50 【问题描述】:对不起,如果我把标题弄错了,我是 Twisted 的新手,无法很好地描述我的问题。
所以问题是,我有一个基于 ircLogBot.py(http://twistedmatrix.com/documents/current/words/examples/ircLogBot.py) 的 IRC 机器人,它通过 php 页面在 IRC 和 mysql 数据库之间中继消息。
它必须每 1 秒加载一个 PHP 页面,解析内容(JSON),循环遍历它,然后将每个项目发布到 IRC。除了将其发布到 IRC 之外,我已经对所有这些进行了排序。
之所以很难,是因为循环在另一个线程中运行(它必须工作),而我不知道如何从该线程调用 msg()。
这个描述可能真的很混乱,所以看看我的代码。我评论了我想发送消息的地方:
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol, threads
from twisted.python import log
# system imports
import time, sys, json, urllib, urllib2, threading
url = 'http://86.14.76.169/root/scripts/ircbot.php'
urltwo = 'http://86.14.76.169/root/scripts/returnchat.php'
class LogBot(irc.IRCClient):
try:
"""A logging IRC bot."""
nickname = "WorldConflictBot"
def connectionMade(self):
irc.IRCClient.connectionMade(self)
def connectionLost(self, reason):
irc.IRCClient.connectionLost(self, reason)
# callbacks for events
def signedOn(self):
"""Called when bot has succesfully signed on to server."""
self.join(self.factory.channel)
def joined(self, channel):
"""This will get called when the bot joins the channel."""
self.action('JackBot', self.factory.channel, 'Joined')
def privmsg(self, user, channel, msg):
"""This will get called when the bot receives a message."""
user = user.split('!', 1)[0]
values =
values = 'type' : 'message',
'message' : msg,
'username' : user,
data = urllib.urlencode(values)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()
# Check to see if they're sending me a private message
if channel == self.nickname:
msg = "It isn't nice to whisper! Play nice with the group."
self.msg(user, msg)
return
# Otherwise check to see if it is a message directed at me
if msg.startswith(self.nickname + ":"):
msg = "%s: Hey :)" % user
self.msg(channel, msg)
def action(self, user, channel, msg):
"""This will get called when the bot sees someone do an action."""
user = user.split('!', 1)[0]
# irc callbacks
def irc_NICK(self, prefix, params):
"""Called when an IRC user changes their nickname."""
old_nick = prefix.split('!')[0]
new_nick = params[0]
values =
values = 'type' : 'nick',
'from' : old_nick,
'to' : new_nick,
data = urllib.urlencode(values)
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
the_page = response.read()
# For fun, override the method that determines how a nickname is changed on
# collisions. The default method appends an underscore.
def alterCollidedNick(self, nickname):
"""
Generate an altered version of a nickname that caused a collision in an
effort to create an unused related name for subsequent registration.
"""
return nickname + '^'
except KeyboardInterrupt:
LogBotLooper.exit()
sys.exit()
class LogBotFactory(protocol.ClientFactory):
"""A factory for LogBots.
A new protocol instance will be created each time we connect to the server.
"""
def __init__(self):
self.channel = 'worldconflict'
def buildProtocol(self, addr):
p = LogBot()
p.factory = self
return p
l = LogBotLooper()
l.factory = self
return l
def clientConnectionLost(self, connector, reason):
"""If we get disconnected, reconnect to server."""
connector.connect()
def clientConnectionFailed(self, connector, reason):
print "connection failed:", reason
reactor.stop()
class LogBotLooper(irc.IRCClient):
def __init__(self):
i = 0
lastid = 0
while 1:
time.sleep(1)
if(i == 0):
values = 'justlastid': 'true'
else:
values = 'lastid' : lastid
data = urllib.urlencode(values)
req = urllib2.Request(urltwo, data)
response = urllib2.urlopen(req)
the_page = response.read()
if(i == 0):
lastid = the_page
i += 1
else:
if(the_page != 'error'):
jsonpage = json.loads(the_page)
for message in jsonpage['messages']:
#Need to send the variable `message` to IRC.
lastid = jsonpage['highestid']
def exit(self):
sys.exit()
if __name__ == '__main__':
try:
# initialize logging
log.startLogging(sys.stdout)
# create factory protocol and application
f = LogBotFactory()
# connect factory to this host and port
reactor.connectTCP("irc.skyirc.net", 6667, f)
reactor.callInThread(LogBotLooper)
# run bot
reactor.run()
except KeyboardInterrupt:
LogBotLooper.exit()
sys.exit()
【问题讨论】:
【参考方案1】:您可能已经知道这一点,但您的协议类应该只坚持事件处理和传输本身的其他抽象。这样你就可以保持关注点的分离,并且你有一个可维护的框架。在 MVC 范式中,您的协议类是一个控制器,甚至可能是一个视图,但绝对不是模型。进行 PHP Web 服务调用可能属于模型。
至于将工作转移到其他线程(对于任何阻塞 I/O,例如 Web 服务调用,您肯定需要这样做),您需要:
from twisted.internet import threads, reactor
从主反应器线程,调用 threads.deferToThread(mycallable, *args, **kwargs)
以从下一个可用的工作线程调用 mycallable
。
从任何工作线程调用 reactor.callFromThread(mycallable, *args, **kwargs)
以从主反应器线程调用 mycallable
。
要将工作从一个工作线程转移到另一个,请结合以下两种技术:reactor.callFromThread(threads.deferToThread, mycallable, *args, **kwargs)
。
我相信这两个调用都会返回一个Deferred
对象(我知道deferToThread
会这样做)。如果将回调添加到延迟,这些回调将在与原始可调用对象相同的线程中执行。要将回调执行委托给工作线程,请在回调中使用上述技术。 (他们不会无缘无故地称其为“扭曲”。)
【讨论】:
讨厌愚蠢,但我不确定我是否理解。我用reactor.callFromThread(sendmsg, message)
替换了#Need to send the variable `message` to IRC.
,但我得到了exceptions.NameError: global name 'sendmsg' is not defined
。我做错了吗?抱歉打扰了。
没关系,我找到了解决方案。我刚刚从一个单独的线程中移动了我的循环并使用了 LoopingCall 方法。 :)【参考方案2】:
如果我没有从您的帖子中收到错误消息,那么我也遇到了和您一样的问题。
http://twistedmatrix.com/documents/10.1.0/core/howto/threading.html
threads.blockingCallFromThread 是这个问题的另一个答案。
只需替换
#Need to send the variable `message` to IRC.
与
threads.blockingCallFromThread(reactor, irc.send_message, message)
#I assume you call irc.send_message here
【讨论】:
以上是关于Twisted/Python - 从另一个线程调用协议内的方法的主要内容,如果未能解决你的问题,请参考以下文章