我已经用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方法呢?答案 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。
reactor.run
所以以下所有我认为适用于你。reactor.callInThread
它将在工作线程中运行。如果你从不这样做,一切都在主反应堆线程中运行,这意味着例如任何I / O操作都将阻塞反应器线程,并且在I / O完成之前你不能接收任何事件。reactor.callFromThread
来执行任何非线程安全的操作。提供一个回调,它将在主反应堆线程中运行。你在这里比对不起更安全,相信我。Deferred
处理。因此,在设置回调时,不要害怕使用partial(reactor.callFromThread, mycallback)
或partial(reactor.callInThread, mycallback)
而不是简单mycallback
。我学到了很难的方法;没有它,我发现在延迟回调中我可能做的任何阻塞I / O都是错误输出(由于线程安全问题)或阻塞主线程。如果你刚开始使用Twisted,这有点像“信任下降”。学会放弃管理自己的线程并通过Queue
对象等传递消息。一旦弄清楚Deferred
和反应堆是如何工作的(因为某种原因,它被称为“扭曲”),它将会对你来说似乎很自然。 Twisted确实迫使你在功能编程风格中解耦和分离问题,但是一旦你完成了,我发现它非常干净并且运行良好。
一个提示:我写了一些装饰器用于我的所有回调函数,这样我就不必经常调用callInThread
和callFromThread
并设置Deferred
进行异常处理整个代码中的回调;我的装饰者为我启用了这种行为。它可能会阻止错误忘记这样做,这肯定使Twisted开发对我来说更加愉快。