从另一个线程调用一个扭曲的协议方法

时间:2013-07-20 00:24:28

标签: python multithreading websocket twisted

我已经用Python创建了一个Home Security程序,它使用Raspberry Pi的GPIO来感知运动并启动警报器。用户使用NFC标签激活/停用系统,也可以使用连接到树莓派的nfc reder。

为此,我需要不间断地检查nfc标签,同时不断检查传感器的运动是否也无阻塞。我需要一些更平行的东西,但我认为这两个足以说明我的观点。

现在我使用我开始/停止的线程 - Stopping a thread after a certain amount of time - 我不确定这是否是最佳方式,但截至目前,系统工作正常。

现在我想扩展其功能,通过websockets提供通知。我发现这可以用Twisted完成,但我很困惑..

以下是我尝试执行此操作的示例代码:

from twisted.internet import reactor
from autobahn.websocket import WebSocketServerFactory, \
                               WebSocketServerProtocol, \
                               listenWS


def thread1(stop_event):
    while(not stop_event.is_set()):
        stop_event.wait(4)
        print "checking sensor"
        # sensor_state = GPIO.input(11)
        if sensor_state == 1:
            # how can I call send_m("sensor detected movement")  #<---
            t1_stop_event.set()

t1_stop_event = Event()
t1 = Thread(target=thread1, args=(t1_stop_event,))

class EchoServerProtocol(WebSocketServerProtocol):
   def onMessage(self, msg, binary):
    print "received: "+msg
    print "stopping thread1"
    t1_stop_event.set()

   def send_m(self, msg):
    self.sendMessage(msg)

if __name__ == '__main__':
   t1.start()
   factory = WebSocketServerFactory("ws://localhost:9000")
   factory.protocol = EchoServerProtocol
   listenWS(factory)
   reactor.run()

那么如何从thread1?

这样的线程调用服务器协议的send方法呢?

2 个答案:

答案 0 :(得分:5)

通常情况下,关于线程和Twisted的问题的答案是“不使用线程”。

您在此处启动线程的原因似乎是您可以重复检查GPIO传感器。检查传感器块?我猜不是,因为如果它是GPIO,它是本地可用的硬件,其结果将立即可用。但我会两种方式给你答案。

你在这里使用线程的主要方法是重复做一些事情。如果你想在Twisted中重复做一些事情,从来没有理由使用线程 :)。 Twisted包含一个用于重复任务的优秀API:LoopingCall。您的示例,重写为使用LoopingCall(再次假设GPIO调用未阻止)将如下所示:

from somewhere import GPIO

from twisted.internet import reactor, task
from autobahn.websocket import WebSocketServerFactory, \
                               WebSocketServerProtocol, \
                               listenWS

class EchoServerProtocol(WebSocketServerProtocol):

    def check_movement(self):
        print "checking sensor"
        sensor_state = GPIO.input(11)
        if sensor_state == 1:
            self.send_m("sensor detected movement")

    def connectionMade(self):
        WebSocketServerProtocol.connectionMade(self)
        self.movement_checker = task.LoopingCall(self.check_movement)
        self.movement_checker.start(4)

    def onMessage(self, msg, binary):
        self.movement_checker.stop()

    def send_m(self, msg):
        self.sendMessage(msg)

if __name__ == '__main__':
   factory = WebSocketServerFactory("ws://localhost:9000")
   factory.protocol = EchoServerProtocol
   listenWS(factory)
   reactor.run()

当然,有一种情况你仍然需要使用线程:如果GPIO检查程序(或任何你的重复任务)需要在一个线程中运行,因为它是一个可能无法在库中阻塞的操作被修改以更好地使用Twisted,并且您不想阻止主循环。

在这种情况下,您仍然希望使用LoopingCall,并利用其中一项功能:如果您从Deferred正在调用的函数返回LoopingCall,那么在Deferred触发之前,它不会再次调用该函数。这意味着您可以将任务传递给线程,而不用担心主循环堆积该线程的查询:您可以在线程完成时自动恢复主线程上的循环。

为了让您更具体地了解我的意思,这里修改的check_movement函数可以处理在线程中运行的长时间阻塞调用,而不是可以在其上运行的快速轮询调用主循环:

def check_movement(self):
    from twisted.internet.threads import deferToThread
    def get_input():
        # this is run in a thread
        return GPIO.input(11)
    def check_input(sensor_state):
        # this is back on the main thread, and can safely call send_m
        if sensor_state == 1:
            self.send_m("sensor movement detected")
    return deferToThread(get_input).addCallback(check_input)

上述示例中的其他所有内容都保持完全相同。

答案 1 :(得分:2)

您的示例中有几个因素在起作用。简短回答:研究this documentation on threads in Twisted

  • 虽然你没有 使用Twisted的reactor来使用协议类(线程和协议实现是分离的),但你已经调用了reactor.run所以以下所有我认为适用于你。
  • 让Twisted为您创建线程。走出框架会让你陷入困境。使用反应器的IPC消息传递没有“公共”API(我认为),所以如果你使用Twisted,你几乎需要一路走。
  • 默认情况下,Twisted不会切换线程来调用回调。要从主反应器线程委托工作线程(即执行阻塞I / O),您不必自己创建线程,使用reactor.callInThread它将在工作线程中运行。如果你从不这样做,一切都在主反应堆线程中运行,这意味着例如任何I / O操作都将阻塞反应器线程,并且在I / O完成之前你不能接收任何事件。
  • 在工作线程中运行的代码应使用reactor.callFromThread来执行任何非线程安全的操作。提供一个回调,它将在主反应堆线程中运行。你在这里比对不起更安全,相信我。
  • 以上所有内容也适用于Deferred处理。因此,在设置回调时,不要害怕使用partial(reactor.callFromThread, mycallback)partial(reactor.callInThread, mycallback)而不是简单mycallback。我学到了很难的方法;没有它,我发现在延迟回调中我可能做的任何阻塞I / O都是错误输出(由于线程安全问题)或阻塞主线程。

如果你刚开始使用Twisted,这有点像“信任下降”。学会放弃管理自己的线程并通过Queue对象等传递消息。一旦弄清楚Deferred和反应堆是如何工作的(因为某种原因,它被称为“扭曲”),它将会对你来说似乎很自然。 Twisted确实迫使你在功能编程风格中解耦和分离问题,但是一旦你完成了,我发现它非常干净并且运行良好。

一个提示:我写了一些装饰器用于我的所有回调函数,这样我就不必经常调用callInThreadcallFromThread并设置Deferred进行异常处理整个代码中的回调;我的装饰者为我启用了这种行为。它可能会阻止错误忘记这样做,这肯定使Twisted开发对我来说更加愉快。