Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python PyQT4 QWidget必须在关闭前接收两次“关闭”信号_Python_Multithreading_Pyqt4 - Fatal编程技术网

Python PyQT4 QWidget必须在关闭前接收两次“关闭”信号

Python PyQT4 QWidget必须在关闭前接收两次“关闭”信号,python,multithreading,pyqt4,Python,Multithreading,Pyqt4,我正在编写一个PyQT4 4.11程序,它执行一个长而慢的任务,理想情况下需要一个进度条。如果我不使用线程,这个程序几乎可以完美地工作,对一个只包含QProgressBar和布局的QWidget进行子分类。将这个子类实例化为表单,我可以调用form.show将它放到屏幕上,然后我的长循环可以通过调用form.progressbar.setValueprogress更新进度。这有两个问题: 如果用户试图与窗口交互,他们会从windows manager/OS桌面进程收到“无响应”消息。这是因为没有

我正在编写一个PyQT4 4.11程序,它执行一个长而慢的任务,理想情况下需要一个进度条。如果我不使用线程,这个程序几乎可以完美地工作,对一个只包含QProgressBar和布局的QWidget进行子分类。将这个子类实例化为表单,我可以调用form.show将它放到屏幕上,然后我的长循环可以通过调用form.progressbar.setValueprogress更新进度。这有两个问题:

如果用户试图与窗口交互,他们会从windows manager/OS桌面进程收到“无响应”消息。这是因为没有处理事件

因为没有处理事件,所以用户不能通过关闭窗口来取消长的慢循环

因此,我尝试在一个单独的线程中运行长而慢的循环,使用一个信号来更新进度条。我重写了我的QWidget的closeEvent,这样它就可以取消与硬件设备的交互,所有这些都封装在互斥锁中,这样设备通信就不会失去同步。同样,这几乎奏效。如果我取消,应用程序将退出。如果我让它运行到完成,我必须手动关闭窗口,即单击关闭图标或按alt-f4,即使我正在向QWidget发送关闭信号。正如您在下面的代码中所看到的,存在一些复杂情况,因为如果取消,应用程序无法立即关闭,因为它必须等待硬件清理。这是我的代码的最低版本

import sys
import os
import time
from PyQt4 import QtCore, QtGui

class Ui_ProgressBarDialog(QtGui.QWidget):
    def __init__(self, on_close=None):
        QtGui.QWidget.__init__(self)
        self.setupUi(self)
        self.center()

        #on_close is a function that is called to cancel
        #the long slow loop
        self.on_close = on_close

    def center(self):
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def setupUi(self, ProgressBarDialog):
        ProgressBarDialog.setObjectName(_fromUtf8("ProgressBarDialog"))
        ProgressBarDialog.resize(400, 33)
        self.verticalLayout = QtGui.QVBoxLayout(ProgressBarDialog)
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.progressBar = QtGui.QProgressBar(ProgressBarDialog)
        self.progressBar.setProperty("value", 0)
        self.progressBar.setObjectName(_fromUtf8("progressBar"))
        self.verticalLayout.addWidget(self.progressBar)

        self.retranslateUi(ProgressBarDialog)

        #This allows the long slow loop to update the progress bar
        QtCore.QObject.connect(
            self,
            QtCore.SIGNAL("updateProgress"),
            self.progressBar.setValue
        )

        #Catch the close event so we can interrupt the long slow loop
        QtCore.QObject.connect(
            self,
            QtCore.SIGNAL("closeDialog"),
            self.closeEvent
        )

        #Repaint the window when the progress bar's value changes  
        QtCore.QObject.connect(
            self.progressBar,
            QtCore.SIGNAL("valueChanged(int)"),
            self.repaint
        )
        QtCore.QMetaObject.connectSlotsByName(ProgressBarDialog)

    def retranslateUi(self, ProgressBarDialog):
        ProgressBarDialog.setWindowTitle("Please Wait")

    def closeEvent(self, event, force=False):
        if self.on_close is not None and not force:
            self.on_close()

app = QtGui.QApplication(sys.argv)
filename = str(QtGui.QFileDialog.getSaveFileName(
    None,
    "Save as",
    os.getcwd(),
    "Data files: (*.dat)"
))

loop_mutex = thread.allocate_lock()
cancelled = False
can_quit = False
result = None

def cancel_download():
    global cancelled
    if can_quit:
        return
    if QtGui.QMessageBox.question(
            None,
            'Cancel Download',
            "Are you sure you want to cancel the download?",
            QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
            QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
        with loop_mutex:
            selected_device.cancelDownload()
            cancelled = True
        while not can_quit:
            time.sleep(0.25)

form = ProgressBarDialog.Ui_ProgressBarDialog(cancel_download)
form.setWindowTitle("Please Wait")
form.progressBar.setMaximum(1000)
form.progressBar.setValue(0)
form.show()

def long_slow_loop(mutex, filename):
    global can_quit, result
    progress = 0

    temp_binary_file = open(filename, "wb")

    #The iterator below does the actual work of interacting with a
    #hardware device, so I'm locking around the "for" line. I must
    #not fetch more data from the device while a cancel command is
    #in progress, and vice versa
    mutex.acquire()
    for data in ComplexIteratorThatInteractsWithHardwareDevice():
        mutex.release()
        temp_binary_file.write(datablock)
        progress += 1
        form.emit(QtCore.SIGNAL("updateProgress"), progress)
        mutex.acquire()
        if cancelled:
            break
    mutex.release()
    result = not cancelled
    temp_binary_file.close()
    if cancelled:
        os.unlink(filename)
    #having set can_quit to True the following emission SHOULD
    #cause the app to exit by closing the last top level window
    can_quit = True
    form.emit(QtCore.SIGNAL("closeDialog"), QtGui.QCloseEvent(), True)

thread.start_new_thread(do_dump, (loop_mutex, filename))
app.exec_()
if result == True:
    QtGui.QMessageBox.information(None, 'Success', "Save to disk successful", QtGui.QMessageBox.Ok)    

事实证明,秘密在于使用一个QThread,当线程退出时它会发出一些信号。完成或终止取决于退出是正常还是异常。这里有一些代码

import sys
import os
import time
from PyQt4 import QtCore, QtGui

class Ui_ProgressBarDialog(QtGui.QWidget):
    def __init__(self, process, parent = None):
        QtGui.QWidget.__init__(self, parent)
        self.thread = process
        self.setupUi(self)
        self.center()
        self.thread.start()

    def center(self):
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def setupUi(self, ProgressBarDialog):
        ProgressBarDialog.setObjectName("ProgressBarDialog")
        ProgressBarDialog.resize(400, 33)
        self.verticalLayout = QtGui.QVBoxLayout(ProgressBarDialog)
        self.verticalLayout.setObjectName("verticalLayout")
        self.progressBar = QtGui.QProgressBar(ProgressBarDialog)
        self.progressBar.setProperty("value", 0)
        self.progressBar.setObjectName("progressBar")
        self.verticalLayout.addWidget(self.progressBar)

        self.retranslateUi(ProgressBarDialog)

        #Close when the thread finishes (normally)
        QtCore.QObject.connect(
            self.thread,
            QtCore.SIGNAL("finished()"),
            self.close
        )

        #Close when the thread is terminated (exception, cancelled etc)
        QtCore.QObject.connect(
            self.thread,
            QtCore.SIGNAL("terminated()"),
            self.close
        )

        #Let the thread update the progress bar position
        QtCore.QObject.connect(
            self.thread,
            QtCore.SIGNAL("updateProgress"),
            self.progressBar.setValue
        )

        #Repaint when the progress bar value changes
        QtCore.QObject.connect(
            self.progressBar,
            QtCore.SIGNAL("valueChanged(int)"),
            ProgressBarDialog.repaint
        )

        QtCore.QMetaObject.connectSlotsByName(ProgressBarDialog)

    def retranslateUi(self, ProgressBarDialog):
        ProgressBarDialog.setWindowTitle("Please Wait")

    def closeEvent(self, event):
        if self.thread.exit_status == self.thread.RUNNING:
            if QtGui.QMessageBox.question(None, 'Cancel Download', "Are you sure you want to cancel the download?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
                if self.thread.exit_status == self.thread.RUNNING:
                    self.thread.exiting = True
                    while self.thread.exiting:
                        time.sleep(0.25)
                elif self.thread.exit_status == self.thread.SUCCESS:
                    self.thread.exit_status = self.thread.CANCELLED
            else:
                if self.thread.exit_status == self.thread.RUNNING:
                    event.ignore()

app = QtGui.QApplication(sys.argv)
filename = str(QtGui.QFileDialog.getSaveFileName(
    None,
    "Save as",
    os.getcwd(),
    "Data files: (*.dat)"
))

class DeviceDataStreamHandler(QtCore.QThread):
    RUNNING = 1
    SUCCESS = 0
    FAILURE = -1
    CANCELLED = -2

    class CancelledError(Exception):
        pass

    def __init__(self, parent = None, **kwargs):
        QtCore.QThread.__init__(self, parent)
        self.exiting = False
        self.exit_status = DeviceDataStreamHandler.RUNNING
        self.device = device
        self.filename = filename

    def run(self):
        progress = 0
        try:
            temp_binary_file = open(self.filename, "wb")
            #getDataStream is an interator returing strings
            for data in self.device.getDataStream():
                temp_binary_file.write(data)
                progress += 1
                self.emit(QtCore.SIGNAL("updateProgress"), progress)
                if self.exiting:
                    raise DeviceDataStreamHandler.CancelledError()
            self.exit_status = DeviceDataStreamHandler.SUCCESS
        except DeviceDataStreamHandler.CancelledError:
            self.exit_status = DeviceDataStreamHandler.CANCELLED
            self.device.cancelDownload()
        except Exception as E:
            self.exit_status = DeviceDataStreamHandler.FAILURE
            self.error_details = str(E)
        finally:
            temp_binary_file.close()
            if self.exit_status == DeviceDataStreamHandler.CANCELLED:
                os.unlink(filename)
        self.exiting = False

class HardwareDeviceObject(object):
    def __init__(self):
        #initialises comms with a hardware device
        self.event_count = 0
        self.capture = False

    def startCapture(self):
        self.capture = True

    def cancelDownload():
        self.capture = False

    def read(self):
        #returns a string sent from the device
        time.sleep(1)
        self.event_count += 1
        return self.event_count

    def getDataStream(self):
        class DataStreamIterator(object):
            def __init__(self, ds, max_capture_count = 100):
                self.ds = ds
                self.capture_count = 0
                self.max_capture_count = max_capture_count

            def __iter__(self):
                return self

            def next(self):
                #return string received from device
                if self.ds.capture and self.capture_count < self.max_capture_count:
                    self.capture_count += 1
                    return self.ds.read()
                else:
                    raise StopIteration()

        self.startCapture()
        return DataStreamIterator(self)

capture = DeviceDataStreamHandler(device = HardwareDeviceObject(), filename = filename)
form = ProgressBarDialog.Ui_ProgressBarDialog(capture)
form.setWindowTitle("Dumping sessions")
form.progressBar.setMaximum(100) #expect 100 outputs from the device
form.progressBar.setValue(0)
form.show()

app.exec_()
if capture.exit_status == DeviceDataStreamHandler.SUCCESS:
    QtGui.QMessageBox.information(None, 'Success', "Save to disk successful", QtGui.QMessageBox.Ok)
elif capture.exit_status == DeviceDataStreamHandler.FAILURE:
    QtGui.QMessageBox.critical(None, 'Error interacting with device', "{}".format(capture.error_details), QtGui.QMessageBox.Ok)

信号与事件不同。无法使用信号发送事件。但是在任何情况下,如果您想关闭窗口,为什么不直接调用form.close呢?