Я действительно опаздываю на вечеринку, но могу дать несколько полезных советов будущим читателям.
Причина, по которой это сложно, заключается в том, что вы пытаетесь заставить два цикла событий работать вместе. У вас есть Twisted реактор и цикл wxWidgets. Есть два способа связать петли
- Используйте особый реактор в Twisted, предназначенный для объединения событий Twisted и wx в один цикл. Twisted был разработан с учетом этого, так что несложно сварить специальный реактор для этой цели.
- Запустите реактор Twisted и цикл событий wx в отдельных потоках. В этом случае вы полагаетесь на то, что операционная система делегирует время выполнения каждому циклу обработки событий.
На самом деле я только сегодня закончил работать с обеими этими стратегиями с Twisted и PyQt. Qt и wxWidgets не настолько отличаются, поэтому я думаю, что вы, вероятно, сможете адаптировать мое решение с минимальными усилиями. Обратите внимание, что здесь я не использую Perspective Broker. Как только вы поймете, как я заставил это работать, добавление слоя Perspective Broker будет очень простым.
Сначала я описываю свое решение с помощью метода №1, основанного на pyqt4reactor. Вот полный рабочий код (вам нужен pyqt4reactor, который можно найти в различных неофициальных местах на сайте interwebz)
Клиент чата со специальным реактором
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, который может излучать один сигнал. В главном окне 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-соединение. Это дает отложенный вызов, который будет вызываться с результирующим протоколом в качестве аргумента. Наша функция обратного вызова 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, который мы хотели бы посылать, когда результат нашей функции доступен, и дополнительные аргументы для нашей функции. Реактор вызывает нашу функцию и прикрепляет к полученному отложенному вызову обратный вызов, который будет испускать предоставленный нами сигнал.
Я надеюсь, что это полезно для кого-то :)
Если кто-то увидит упрощения, дайте мне знать.
person
DanielSank
schedule
23.09.2013