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 - 从另一个线程调用协议内的方法的主要内容,如果未能解决你的问题,请参考以下文章

从另一个线程调用 Boost 线程 Sleep_For()

C# 从另一个线程调用 form.show()

使用线程从另一个成员函数调用一个成员函数

从另一个线程调用 CoreData ContextObjectsDidChangeNotification

如何使用线程从另一个类调用不同的函数

避免多次调用 Invoke 以从另一个线程更新 GUI