在Twisted中使用我自己的主循环
我有一个现成的程序,它有自己的主循环,并根据收到的输入进行计算——为了简单起见,我们假设输入来自用户。我现在想把计算过程从本地转移到远程进行,所以我决定在Twisted中实现远程过程调用(RPC)。
理想情况下,我只想修改我的一个函数,比如说 doComputation()
,让它调用Twisted来执行RPC,获取结果后再返回。程序的其他部分应该保持不变。但是,我该怎么做呢?当我调用 reactor.run()
时,Twisted会接管主循环。我还了解到,Twisted中并没有真正的线程,所有任务都是按顺序运行的,所以我似乎不能仅仅创建一个循环调用(LoopingCall)来以这种方式运行我的主循环。
2 个回答
看起来这里正确且非常简单的答案是使用一个循环调用(LoopingCall):
http://www.saltycrane.com/blog/2008/10/running-functions-periodically-using-twisteds-loopingcall/
from datetime import datetime
from twisted.internet.task import LoopingCall
from twisted.internet import reactor
def doComputation():
print "Custom fn run at", datetime.now()
lc = LoopingCall(doComputation)
lc.start(0.1) # run your own loop 10 times a second
# put your other twisted here
reactor.run()
你有几种不同的选择,这取决于你现有程序的主循环是什么样的。
如果你的主循环是来自一个图形用户界面(GUI)库,Twisted可能已经支持它。在这种情况下,你可以直接使用它。
你也可以自己写一个反应器(reactor)。虽然这方面的文档不多,但你可以看看qtreactor是如何在Twisted外部实现反应器插件的。
你还可以使用threadedselectreactor
来写一个简单的反应器。这方面的文档也不多,但wxpython反应器就是用它实现的。个人来说,我不推荐这种方法,因为测试起来比较困难,可能会导致一些混乱的竞争条件,但它的好处是可以让你几乎使用Twisted的所有默认网络代码,只需加一层薄薄的封装。
如果你真的确定不想让你的doComputation
是异步的,并且希望程序在等待Twisted回应时阻塞,可以按照以下步骤操作:
- 在主循环启动之前,在另一个线程中启动Twisted,像这样:
twistedThread = Thread(target=reactor.run); twistedThread.start()
- 在你自己主循环的线程中实例化一个对象来进行RPC通信(比如叫
RPCDoer
),这样你就可以引用它。确保用reactor.callFromThread
来启动它的Twisted逻辑,这样你就不需要为所有的Twisted API调用加上封装。 - 实现
RPCDoer.doRPC
,让它返回一个Deferred,只使用Twisted的API调用(也就是说,不要调用你现有的应用程序代码,这样就不需要担心你的应用对象的线程安全;把doRPC
所需的信息作为参数传递给它)。 现在你可以这样实现
doComputation
:def doComputation(self): rpcResult = blockingCallFromThread(reactor, self.myRPCDoer.doRPC) return self.computeSomethingFrom(rpcResult)
- 记得在主循环的关闭程序中调用
reactor.callFromThread(reactor.stop); twistedThread.join()
,否则你可能会在退出时看到一些混乱的追踪信息或日志消息。
最后,有一个你真的应该考虑的选项,特别是从长远来看:放弃你现有的主循环,想办法直接使用Twisted的主循环。根据我的经验,这对10个问这个问题的人中有9个来说是正确的答案。我并不是说这总是是最佳选择——有很多情况下你确实需要保留自己的主循环,或者去掉现有循环的工作量太大。但维护自己的循环也是一项工作。请记住,Twisted的循环经过了数百万用户的广泛测试,并在各种环境中使用。如果你的循环也非常成熟,那可能没什么大问题,但如果你正在编写一个小的、新的程序,可靠性上的差异可能会很显著。