我正在编写一个通过串行连接与硬件通信的PySide应用程序。
我有一个启动设备命令的按钮和一个标签,用于向用户显示结果。 现在有些设备需要很长时间(多秒)才能回复请求,这会冻结GUI。我正在寻找一种简单的机制来在后台线程或类似的线程中运行调用。
我创建了一个我想要完成的简短示例:
import sys
import time
from PySide import QtCore, QtGui
class Device(QtCore.QObject):
def request(self, cmd):
time.sleep(3)
return 'Result for {}'.format(cmd)
class Dialog(QtGui.QDialog):
def __init__(self, device, parent=None):
super().__init__(parent)
self.device = device
self.layout = QtGui.QHBoxLayout()
self.label = QtGui.QLabel('--')
self.button = QtGui.QPushButton('Go')
self.layout.addWidget(self.label)
self.layout.addWidget(self.button)
self.setLayout(self.layout)
self.button.clicked.connect(self.go)
def go(self):
self.button.setEnabled(False)
# the next line should be called in the
# background and not freeze the gui
result = self.device.request('command')
self.label.setText(result)
self.button.setEnabled(True)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dev = Device()
win = Dialog(device=dev)
win.show()
win.raise_()
app.exec_()
我希望拥有的是某种功能,如:
result = nonblocking(self.device.request, 'command')
应该提出异常,就好像我直接调用了函数一样。
任何想法或建议?
答案 0 :(得分:1)
线程是最好的方法。 Python线程也很容易使用。 Qt线程与python线程的工作方式不同。 http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
我只是使用python线程。此外,如果您使用串行端口连接,您可能希望将数据拆分为线程安全的队列。
import time
import threading
import queue
import serial
def read_serial(serial, storage):
while True:
value = serial.readline()
storage.put(value) # or just process the data
ser = serial.SerailPort("Com1", 9600)
stor = queue.Queue()
th = threading.Thread(target=read_serial, args=(ser, stor))
th.start()
for _ in range(10):
time.sleep(1)
th.join(0)
ser.close()
# Read queue
您可以做的另一件事是对串行端口和QObject使用多重继承。这允许您使用Qt信号。下面是一个非常粗略的例子。
class SerialThread(object):
def __init__(self, port=None, baud=9600):
super().__init__()
self.state = threading.Condition() # Notify threading changes safely
self.alive = threading.Event() # helps with locking
self.data = queue.Queue()
self.serial = serial.Serial()
self.thread = threading.Thread(target=self._run)
if port is not None:
self.connect(port, baud)
# end Constructor
def connect(self, port, baud):
self.serial.setPort(port)
self.serial.setBaudrate(baud)
self.serial.open()
with self.state:
self.alive.set()
self.thread.start()
# end connect
def _run(self):
while True:
with self.state:
if not self.alive.is_set():
return
self.read()
# end _run
def read(self):
serstring = bytes("", "ascii")
try:
serstring = self.serial.readline()
except:
pass
else:
self.process_read(serstring)
return serstring # if called directly
# end read
def process_read(self, serstring):
if self.queue.full():
self.queue.get(0) # remove the first item to free up space
self.queue.put(serstring)
# end process_read
def disconnect(self):
with self.state:
self.alive.clear()
self.state.notify()
self.thread.join(0) # Close the thread
self.serial.close() # Close the serial port
# end disconnect
# end class SerialThread
class SerialPort(SerialThread, QtCore.QObject):
data_updated = QtCore.Signal()
def process_read(self, serstring):
super().process_read(serstring)
self.data_updated.emit()
# end process_read
# end class SerialPort
if __name__ == "__main__":
ser = SerialPort()
ser.connect("COM1", 9600)
# Do something / wait / handle data
ser.disconnect()
ser.queue.get() # Handle data
始终确保在退出时正确关闭并断开所有连接。另请注意,线程只能运行一次,因此您可能需要查看可调用的线程示例How to start and stop thread?。 您也可以通过Qt信号发出数据,而不是使用队列来存储数据。
答案 1 :(得分:1)
我希望拥有的是某种功能,如:
result = nonblocking(self.device.request, 'command')
你在这里要求的实际上是不可能的。你不能有一个非阻塞的呼叫立即返回结果!根据定义,这将是一个阻塞呼叫。
非阻塞调用看起来像:
self.thread = inthread(self.device.request, 'command', callback)
self.device.request
在串行请求完成时运行callback
函数/方法。但是,从线程中天真地调用callback()
会使回调在线程中运行,如果你要从callback
方法调用Qt GUI,这是非常非常糟糕的(Qt GUI方法是不是线程安全的)。因此,您需要一种在MainThread中运行回调的方法。
我对这些函数有类似的需求,并且(与同事)创建了一个库(称为qtutils),其中包含一些不错的包装函数(包括{{1的实现) }})。它的工作原理是使用inthread
将事件发布到MainThread。该事件包含对要运行的函数的引用,并且事件处理程序(驻留在主线程中)执行该函数。因此,您的QApplication.postEvent()
方法将如下所示:
request
其中def request(self, cmd, callback):
time.sleep(3)
inmain(callback, 'Result for {}'.format(cmd))
向主线程发布事件并运行inmain()
。
可以找到此库的文档here,或者您也可以根据上面的大纲设置自己的文档。如果您选择使用我的库,可以通过pip或easy_install安装它。
注意:使用此方法的一个警告是callback(data)
leaks memory in PySide。这就是我现在使用PyQt的原因!