18.8.1.1. Execution of Python signal handlers
A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction). This has consequences:
[...]
A long-running calculation implemented purely in C (such as regular expression matching on a large body of text) may run uninterrupted for an arbitrary amount of time, regardless of any signals received. The Python signal handlers will be called when the calculation finishes.
Pętla zdarzeń Qt jest zaimplementowana w C (++). Oznacza to, że podczas działania i bez wywoływania kodu Pythona (np. Przez sygnał Qt podłączony do szczeliny Pythona), sygnały są odnotowywane, ale procedury obsługi sygnałów w Pythonie nie są wywoływane.
Ale, ponieważ Python 2.6 w Pythonie i 3 może być przyczyną Qt uruchomić funkcję Pythona, gdy odbierany jest sygnał z obsługi za pomocą signal.set_wakeup_fd()
.
Jest to możliwe, ponieważ w przeciwieństwie do dokumentacji, obsługa sygnału niskiego poziomu nie tylko ustawia flagę dla maszyny wirtualnej, ale może również zapisywać bajt w deskryptorze pliku ustawionym przez set_wakeup_fd()
. Python 2 zapisuje bajt NUL, Python 3 zapisuje numer sygnału.
Więc przez instacji klasy Qt, która pobiera deskryptor pliku i dostarcza sygnał readReady()
, jak na przykład QAbstractSocket
, pętla zdarzenie będzie wykonywał funkcję Pythona za każdym razem sygnał (z obsługi) jest odbierany powodując obsługi sygnału wykonać prawie natychmiast bez konieczności timerów:
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_())
Nigdy nie rozumiałem, dlaczego prawie każdy skrypt Pythona na świecie zatrzymuje się za pomocą kontrolki + c z wyjątkiem aplikacji pyqt. Bez wątpienia istnieje ku temu powód, ale ostatecznie jest to bardzo denerwujące. – tokland
@tokland: niech rozwiązać ten problem raz na zawsze :) –
to pojawia się problem design: http://www.mail-archive.com/[email protected]/msg13757.html. Każde rozwiązanie obejmujące wyjątki lub podobne odczucia są po prostu odrażające :-( – tokland