ここでのパーティーには本当に遅れましたが、将来の読者に役立つアドバイスを提供できます。
これが難しい理由は、2 つのイベント ループを連携させようとしているからです。Twisted リアクターと wxWidgets ループがあります。ループをメッシュするには 2 つの方法があります
- Twisted イベントと wx イベントを 1 つのループに結合するように設計された Twisted の特別なケースのリアクターを使用します。Twisted はこれを念頭に置いて設計されているため、この目的のためにカスタム リアクターを作成することはそれほど難しくありません。
- Twisted リアクターと wx イベント ループを別のスレッドで実行します。この場合、実行時間を各イベント ループに委任するためにオペレーティング システムに依存しています。
実は今日、これらの戦略の両方が Twisted と PyQt で機能するようになりました。Qt と wxWidgets はそれほど違いはないので、おそらく最小限の労力で私のソリューションを適応させることができると思います。ここでは Perspective Broker を使用していないことに注意してください。これを機能させる方法を理解すれば、Perspective Broker レイヤーを追加するのは非常に簡単になります。
最初に、pyqt4reactor に依存する方法 #1 を使用して私のソリューションを説明します。完全な作業コードは次のとおりです (interwebz のさまざまな非公式の場所にある pyqt4reactor が必要です)。
特別な原子炉を持つチャット クライアント
import sys
import PyQt4.QtGui as QtGui
import PyQt4.QtCore as QtCore
import PyQt4.uic as uic
import twisted.internet.defer as defer
import twisted.internet.protocol as protocol
import qt4reactor
import constants as C
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = uic.loadUi('ui.ui')
self.ui.sendButton.clicked.connect(self.sendMessage)
self.ui.inputBox.returnPressed.connect(self.sendMessage)
self.ui.connectButton.clicked.connect(self.getNetworkConnection)
self.ui.show()
def getNetworkConnection(self):
#This line should probably not be inside getNetworkConnection
factory = protocol.ClientCreator(reactor, ChatProtocol)
d = factory.connectTCP(C.HOST, C.PORT)
def onConnected(p):
self.cxn = p
p.emitter.signal.connect(self.onNewData)
self.ui.connectButton.setEnabled(False)
d.addCallback(onConnected)
def onNewData(self, data):
self.ui.outputBox.append(data)
def sendMessage(self):
message = str(self.ui.inputBox.text())
self.ui.inputBox.clear()
self.cxn.send(message)
class Emitter(QtCore.QObject):
signal = QtCore.pyqtSignal(str)
def __init__(self):
QtCore.QObject.__init__(self)
class ChatProtocol(protocol.Protocol):
def __init__(self):
self.emitter = Emitter()
def dataReceived(self, data):
self.emitter.signal.emit(data)
def send(self, data):
self.transport.write(data)
class ChatFactory(protocol.ClientFactory):
protocol = ChatProtocol
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
qt4reactor.install()
from twisted.internet import reactor
mainWindow = MainWindow()
reactor.run()
ChatProtocol
とそのヘルパー クラスを調べてみましょうEmitter
。
class ChatProtocol(protocol.Protocol):
def __init__(self):
self.emitter = Emitter()
def dataReceived(self, data):
self.emitter.signal.emit(data)
def send(self, data):
self.transport.write(data)
class Emitter(QtCore.QObject):
signal = QtCore.pyqtSignal(str)
プロトコル自体は実にシンプルです。呼び出す.send
と、そのトランスポートにデータが書き込まれます。
データ受信はもう少し複雑です。Twisted コードが Qt イベント ループに着信チャットを通知するようにするために、単一のシグナルを発することができる QObject である Emitter を Protocol に付与します。Qt のメイン ウィンドウで、チャット ウィンドウにデータを投稿するようにこのシグナルを接続します。このフックアップは、接続を確立するときに発生します。調べてみましょう:
class MainWindow(QtGui.QMainWindow):
<snip>
def getNetworkConnection(self):
#This line should probably not be inside getNetworkConnection
factory = protocol.ClientCreator(reactor, ChatProtocol)
d = factory.connectTCP(C.HOST, C.PORT)
def onConnected(p):
self.cxn = p
p.emitter.signal.connect(self.onNewData)
self.ui.connectButton.setEnabled(False)
d.addCallback(onConnected)
クライアント ファクトリに TCP 接続を行うように指示します。これにより、結果のプロトコルを引数として呼び出される deferred が得られます。コールバック関数onConnected
には、そのプロトコルのエミッターのシグナルを に接続する役割がありますonNewData
。これは、プロトコルのエミッターが呼び出されるたびに発生するたびdataReceived
に、データが Qt シグナル/スロット システムに伝達され、outputBox に表示されることを意味します。残りの関数は多かれ少なかれ意味を成すはずです。
まだ私と一緒に?もしそうなら、スレッドでこれを行う方法を示します。これが完全な作業コードです
スレッドを持つチャット クライアント
import sys
import PyQt4.QtGui as QtGui
import PyQt4.QtCore as QtCore
import PyQt4.uic as uic
import twisted.internet.reactor as reactor
import twisted.internet.defer as defer
import twisted.internet.protocol as protocol
import constants as C
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = uic.loadUi('ui.ui')
self.ui.sendButton.clicked.connect(self.sendMessage)
self.ui.inputBox.returnPressed.connect(self.sendMessage)
self.ui.connectButton.clicked.connect(self.getNetworkConnection)
self.ui.show()
self.networkThread = NetworkThread()
self.networkThread.start()
self.connect(self.networkThread,
self.networkThread.sigConnected,
self.onConnected)
def getNetworkConnection(self):
#This line should probably not be inside getNetworkConnection
factory = protocol.ClientCreator(reactor, ChatProtocol)
self.networkThread.callFromMain(factory.connectTCP,
self.networkThread.sigConnected,
C.HOST, C.PORT)
def onConnected(self, p):
self.cxn = p
p.emitter.signal.connect(self.onNewData)
self.ui.connectButton.setEnabled(False)
def onNewData(self, data):
self.ui.outputBox.append(data)
def sendMessage(self):
message = str(self.ui.inputBox.text())
self.networkThread.callFromMain(self.cxn.send, None, message)
self.ui.inputBox.clear()
class NetworkThread(QtCore.QThread):
"""Run the twisted reactor in its own thread"""
def __init__(self):
QtCore.QThread.__init__(self)
self.sigConnected = QtCore.SIGNAL("sigConnected")
def run(self):
reactor.run(installSignalHandlers=0)
def callFromMain(self, func, successSignal, *args):
"""Call an async I/O function with a Qt signal as it's callback"""
def succeed(result):
self.emit(successSignal, result)
def wrapped():
d = defer.maybeDeferred(func, *args)
if successSignal is not None:
d.addCallback(succeed)
reactor.callFromThread(wrapped)
class Emitter(QtCore.QObject):
#Not sure why I specified a name here...
signal = QtCore.pyqtSignal(str, name='newData')
class ChatProtocol(protocol.Protocol):
def __init__(self):
self.emitter = Emitter()
def dataReceived(self, data):
self.emitter.signal.emit(data)
def send(self, data):
self.transport.write(data)
class ChatFactory(protocol.ClientFactory):
protocol = ChatProtocol
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
QThread で実行されるリアクター以外の興味深い違いは、コードの Twisted 部分でコールバックを接続する方法にあります。特に、ヘルパー関数を使用しますcallFromMain
def callFromMain(self, func, successSignal, *args):
"""Call an async I/O function with a Qt signal as it's callback"""
def succeed(result):
self.emit(successSignal, result)
def wrapped():
d = defer.maybeDeferred(func, *args)
if successSignal is not None:
d.addCallback(succeed)
reactor.callFromThread(wrapped)
Twisted スレッドで呼び出したい関数、関数の結果が利用可能になったときに発行したい Qt シグナル、および関数の追加の引数を提供します。リアクターは私たちの関数を呼び出し、提供されたシグナルを発する結果の deferred にコールバックをアタッチします。
これが誰かに役立つことを願っています:)
誰かが単純化されているのを見たら、私に知らせてください。