GNU/Linux >> Znalost Linux >  >> Linux

Jaký je správný způsob, jak zajistit, aby se moje aplikace PyQt ukončila při zabití z konzoly (Ctrl-C)?

17.4. signál — Nastavení obslužných rutin pro asynchronní události

I když jsou ovladače Python signálů volány asynchronně, pokud jde o uživatele Pythonu, mohou se vyskytovat pouze mezi „atomickými“ instrukcemi interpretu Pythonu. To znamená, že signály přicházející během dlouhých výpočtů implementovaných čistě v C (jako jsou shody regulárních výrazů na velkých částech textu) mohou být zpožděny o libovolně dlouhou dobu.

To znamená, že Python nemůže zpracovávat signály, když běží smyčka událostí Qt. Pouze při spuštění interpretru Pythonu (když se QApplication ukončí nebo když je z Qt zavolána funkce Pythonu), bude zavolána obsluha signálu.

Řešením je použít QTimer, který umožní čas od času spustit tlumočníka.

Všimněte si, že pokud v níže uvedeném kódu nejsou žádná otevřená okna, aplikace se po okně se zprávou ukončí bez ohledu na volbu uživatele, protože QApplication.quitOnLastWindowClosed() ==True. Toto chování lze změnit.

import signal
import sys

from PyQt4.QtCore import QTimer
from PyQt4.QtGui import QApplication, QMessageBox

# Your code here

def sigint_handler(*args):
    """Handler for the SIGINT signal."""
    sys.stderr.write('\r')
    if QMessageBox.question(None, '', "Are you sure you want to quit?",
                            QMessageBox.Yes | QMessageBox.No,
                            QMessageBox.No) == QMessageBox.Yes:
        QApplication.quit()

if __name__ == "__main__":
    signal.signal(signal.SIGINT, sigint_handler)
    app = QApplication(sys.argv)
    timer = QTimer()
    timer.start(500)  # You may change this if you wish.
    timer.timeout.connect(lambda: None)  # Let the interpreter run each 500 ms.
    # Your code here.
    sys.exit(app.exec_())

Dalším možným řešením, jak ukazuje LinearOrbit, je signal.signal(signal.SIGINT, signal.SIG_DFL) , ale neumožňuje vlastní obslužné nástroje.


Pokud si jednoduše přejete, aby ctrl-c aplikaci zavřel – aniž byste k tomu byli „milí“/půvabní – pak z http://www.mail-archive.com/[email protected]/msg13758.html můžete použít toto:

import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)

import sys
from PyQt4.QtCore import QCoreApplication
app = QCoreApplication(sys.argv)
app.exec_()

Zdá se, že to funguje na Linuxu, Windows a OSX - zatím jsem to testoval pouze na Linuxu (a funguje).


18.8.1.1. Spouštění obslužných rutin Python signálů

Obslužná rutina signálu Pythonu se neprovádí uvnitř obslužné rutiny nízkoúrovňového (C) signálu. Místo toho obsluha nízkoúrovňového signálu nastaví příznak, který říká virtuálnímu stroji, aby provedl odpovídající obsluhu signálu Pythonu později (například v další instrukci bajtového kódu). To má důsledky:
[...]
Dlouhodobý výpočet implementovaný čistě v C (jako je shoda regulárních výrazů na velkém množství textu) může běžet bez přerušení po libovolně dlouhou dobu, bez ohledu na přijaté signály. Po dokončení výpočtu budou volány obslužné rutiny signálů Pythonu.

Smyčka událostí Qt je implementována v C(++). To znamená, že zatímco běží a není volán žádný Python kód (např. signálem Qt připojeným k Python slotu), signály jsou zaznamenány, ale nejsou volány ovladače signálů Pythonu.

Ale , od Pythonu 2.6 a v Pythonu 3 můžete způsobit, že Qt spustí funkci Pythonu, když je přijat signál s handlerem pomocí signal.set_wakeup_fd() .

To je možné, protože na rozdíl od dokumentace nízkoúrovňový obslužný program signálu nenastavuje pouze příznak pro virtuální počítač, ale může také zapsat bajt do deskriptoru souboru nastaveného pomocí set_wakeup_fd() . Python 2 zapíše bajt NUL, Python 3 zapíše číslo signálu.

Takže podtřídou třídy Qt, která přebírá deskriptor souboru a poskytuje readReady() signál, jako např. QAbstractSocket , smyčka událostí provede funkci Pythonu pokaždé, když je přijat signál (s obslužnou rutinou), což způsobí, že se obsluha signálu spustí téměř okamžitě bez potřeby časovačů:

import sys, signal, socket
from PyQt4 import QtCore, QtNetwork

class SignalWakeupHandler(QtNetwork.QAbstractSocket):

    def __init__(self, parent=None):
        super().__init__(QtNetwork.QAbstractSocket.UdpSocket, parent)
        self.old_fd = None
        # Create a socket pair
        self.wsock, self.rsock = socket.socketpair(type=socket.SOCK_DGRAM)
        # Let Qt listen on the one end
        self.setSocketDescriptor(self.rsock.fileno())
        # And let Python write on the other end
        self.wsock.setblocking(False)
        self.old_fd = signal.set_wakeup_fd(self.wsock.fileno())
        # First Python code executed gets any exception from
        # the signal handler, so add a dummy handler first
        self.readyRead.connect(lambda : None)
        # Second handler does the real handling
        self.readyRead.connect(self._readSignal)

    def __del__(self):
        # Restore any old handler on deletion
        if self.old_fd is not None and signal and signal.set_wakeup_fd:
            signal.set_wakeup_fd(self.old_fd)

    def _readSignal(self):
        # Read the written byte.
        # Note: readyRead is blocked from occuring again until readData()
        # was called, so call it, even if you don't need the value.
        data = self.readData(1)
        # Emit a Qt signal for convenience
        self.signalReceived.emit(data[0])

    signalReceived = QtCore.pyqtSignal(int)

app = QApplication(sys.argv)
SignalWakeupHandler(app)

signal.signal(signal.SIGINT, lambda sig,_: app.quit())

sys.exit(app.exec_())

Linux
  1. Použití Redditu z konzole v roce 2020

  2. Změna rozložení/mapování klávesnice na konzole (tty) i na X agnostickým způsobem X/konzole?

  3. Proč při psaní Ctrl-c v terminálu není úloha v popředí ukončena, dokud není dokončena?

  1. Python – správný způsob, jak upgradovat Pip v Stretch Debianu?

  2. Jaký je nejlepší způsob, jak vyslat signál všem členům procesní skupiny?

  3. Jaký je nejjednodušší způsob, jak SSH používat Python?

  1. Jaký je správný způsob, jak spustit službu mongod na linux / OS X?

  2. Jaký je správný způsob instalace jdk na linux

  3. Při volání z terminálu použijte místo instalace Anaconda výchozí Python