M. P.
M. P.

Reputation: 59

What is the correct way to terminate a running process when you exit the GUI in pyqt5?

When I click the qbtn button to quit the GUI, the GUI goes away, but the process in convert(filename) continues running in the background.

How can I make it so that when a user presses the exit button, the GUI disappears and any running process terminates?

I am using pyqt5 GUI (below):

from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPixmap, QFont, QColor, QIcon
from PyQt5 import QtCore
from PyQt5 import QtGui
import sys
from pathlib import Path
import threading, queue, time

file_queue = queue.Queue()

def get_logo_path():
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = Path(sys._MEIPASS).joinpath('files').absolute()
    except Exception:
        base_path = Path(".").absolute()

    logo_path = base_path.joinpath('pi_logo.png')

    return str(logo_path.absolute())

class WorkerThread(QtCore.QRunnable):

    __keep_alive = True

    def __init__(self):
        super().__init__()
        self.queue = queue.Queue()


    def addJob(self,filepath):

        # Puts an item into the queue; if the queue is full, wait until a free slot is available before adding the item.
        self.queue.put(filepath)

    def run(self):
        while self.__keep_alive:

            # Remove and return an item from the queue. If queue is empty, wait until an item is available.
            convert(self.queue.get()) 

    def kill(self):
        self.__keep_alive = False

        # Calls the function that puts item into the queue
        self.addJob('')
        print("que: ", self.queue)

def convert(filename):

    #: main
    if filename == '':
        print("CONVERT() run when filename == ''")
        #return

    else:
        print("filename", filename)

        i2 = 1
        while i2 > 0 and i2 < 2500000:
            print("Hello, running process convert() (", str(i2), ")")
            i2 = i2 + 1



class TitleBar(QHBoxLayout):
    __layout = None

    def __init__(self):
        super().__init__()

        label = QLabel()
        pixmap = QPixmap(get_logo_path())
        label.setPixmap(pixmap)
        label.setStyleSheet("QLabel { background-color: #646464;}")
        self.addWidget(label)
        qbtn = QPushButton('X')
        qbtn.setFont(QFont("Arial", weight=QFont.Bold))
        qbtn.setStyleSheet("QPushButton { background-color: #641010; color: #FFFFFF;}")
        qbtn.setMaximumWidth(25)

        # Calls quit() when the ex button is clicked
        qbtn.clicked.connect(self.close_window)
        self.setContentsMargins(10, 10, 10, 10)
        self.addWidget(qbtn)

    def close_window(self):
        print('DEF CLOSE_WINDOW(SELF): QUITTING GUI ONLY!!!!')
        app = QApplication.instance()
        for widget in app.topLevelWidgets():
            print("widget: ", widget)
            if isinstance(widget, QMainWindow):
                # Closes the window
                widget.close()

class BodyLayout(QHBoxLayout):
    __navigation = None
    __body = None

    def __init__(self):
        super().__init__()
        label = QLabel(
            'Drag and drop any directory into the window to begin conversion.'
        )
        label.setStyleSheet(
            "QLabel { background-color: #646464; color: #FFFFFF;}")
        label.setAlignment(QtCore.Qt.AlignCenter)
        self.addWidget(label)

class MainWidget(QWidget):
    __layout = None
    __titlebar = None
    __body = None

    def __init__(self):
        super().__init__()

        self.__titlebar = TitleBar()
        self.__body = BodyLayout()

        self.__layout = QVBoxLayout()
        self.__layout.setContentsMargins(0, 0, 0, 0)
        self.__layout.addLayout(self.__titlebar, 1)
        self.__layout.addLayout(self.__body, 10)
        self.setLayout(self.__layout)

class MainWindow(QMainWindow):
    __window = None
    oldPos = None
    oldY = None
    oldX = None
    workerThread = None
    threadPool = None

    def __init__(self):
        super().__init__()

        self.__window = MainWidget()

        self.setCentralWidget(self.__window)

        sizeObject = QDesktopWidget().screenGeometry(-1)

        self.setFixedSize(min(600, sizeObject.width()),
                          min(150, sizeObject.height()))

        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)

        self.setStyleSheet("QMainWindow { background: #646464; }")

        self.setAcceptDrops(True)

        self.workerThread = WorkerThread()
        self.threadPool = QtCore.QThreadPool()
        self.threadPool.start(self.workerThread)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            filepath = Path(url.toLocalFile())
            #if filepath.name.endswith('.xlsx'):
            if filepath.is_dir():
                self.workerThread.addJob(filepath)

    def mousePressEvent(self, event):
        self.oldPos = event.globalPos()

        if self.size().height() + self.pos().y() - self.oldPos.y() < 15:
            self.oldY = event.globalPos()
        else:
            self.oldY = None

        if self.size().width() + self.pos().x() - self.oldPos.x() < 15:
            self.oldX = event.globalPos()
        else:
            self.oldX = None

        if self.oldPos.y() - self.pos().y() > 60:
            self.oldPos = None

    def mouseReleaseEvent(self, event):
        self.oldPos = None
        self.oldY = None
        self.oldX = None

    def mouseMoveEvent(self, event):
        if self.oldPos != None:
            delta = QtCore.QPoint(event.globalPos() - self.oldPos)
            self.move(self.x() + delta.x(), self.y() + delta.y())
            self.oldPos = event.globalPos()
        if self.oldY != None:
            delta = QtCore.QPoint(event.globalPos() - self.oldY)
            self.setFixedHeight(self.size().height() + delta.y())
            self.oldY = event.globalPos()
        if self.oldX != None:
            delta = QtCore.QPoint(event.globalPos() - self.oldX)
            self.setFixedWidth(self.size().width() + delta.x())
            self.oldX = event.globalPos()

    def closeEvent(self,event):
        print('DEF CLOSEEVENT() kills workerThread')
        self.workerThread.kill()
        #self.quit()

app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())

Upvotes: 0

Views: 248

Answers (1)

musicamante
musicamante

Reputation: 48231

The thread continues because it will not be able to process the queue until convert returns from its while loop.

If you want to also be able to "kill" that, you'll have to constantly check the quit condition at each while loop.

A simple solution is to move the convert function directly within the run() function. BUT... note that the "killing" won't happen immediately, as some already scheduled process is still going to happen (in this case, the printing), and this is actually good, as there's no nice way to kill a process - and that's good for a moltitude of reason I won't explain here.

    def run(self):
        while self.__keep_alive:

            filename = self.queue.get()
            if filename == '':
                print("CONVERT() run when filename == ''")
                break
                #return

            else:
                print("filename", filename)

                i2 = 1
                while i2 > 0 and i2 < 2500000:
                    try:
                        # a non blocking get that constantly checks the queue
                        result = self.queue.get(False)
                        if result == '':
                            break
                    except:
                        pass
                    print("Hello, running process convert() (", str(i2), ")")
                    i2 = i2 + 1
                else:
                    continue
                break

Of course this is a very basic implementation, you might probably want to create some sort of delayed queue (that behaves as a request "buffer") for any other queueing that might happen while the conversion is in place, so that you can process it later, but that's not the scope of this question.

Upvotes: 1

Related Questions