c't 13/2019
S. 190
Know-how
PP: Netzwerk-Threads mit PyQt5
Aufmacherbild
Bild: Albert Hulm

Diener zweier Threads

Netzwerk-Threads in PyQt5-Anwendungen

Parallele Anwendungen haben Tücken. Bei Netzwerkanfragen in grafischen Programmen mit PyQt5 kommt man um Threads jedoch kaum herum. Ich habe mir mit einer scheinbar naheliegenden Implementierung selbst ins Knie geschossen. Aber ich zeige auch, wie man es ohne Zusatzaufwand besser macht.

Bei der Android-App zum Kalorien zählen „Lose It!“ fragte ich mich zuletzt, ob sie ihre Daten auch wie beworben mit Google-Fit synchronisiert. Also schrieb ich mir ein Programm mit PyQt5, das Google Fit abfragt und alle Nahrungsmitteleinträge der letzten Woche anzeigt [1]. Für den Zugriff auf das REST-API sind natürlich Netzwerkanfragen nötig und als brave Programmiererin wusste ich: Netzwerkanfragen sollten man nie im Haupt-Thread eines grafischen Programms absetzen, weil diese das Interface blockieren, bis die Antwort da ist.

class RequestThread(Thread):

  def __init__(self, session, url, callback, *args):
    super(RequestThread, self).__init__()
    self.callback = callback
    self.session = session
    self.url = url
  def run(self):
    response = self.session.request(
      method="GET", url=self.url)
    self.callback(response.json())
    super(RequestThread, self).run()
Oben die Implementierung, bei der Veränderungen am Interface im falschen Thread versauern. Unten die Lösung mit Qts Signal-Slot-Mechanismus.
class RequestThread(Thread, QObject):
  data_loaded = pyqtSignal(list)
  def __init__(self, session, url, *args):
    super(RequestThread, self).__init__()
    QObject.__init__(self, *args)
    self.session = session
    self.url = url
  def run(self):
    response = self.session.request(
      method="GET", url=self.url)
    self.data_loaded.emit(response.json())
    super(RequestThread, self).run()

Zum Glück bringt Python die Klasse Thread mit, mit der ich in ein paar Zeilen einen eigenen Thread für den Request definieren kann (siehe Kasten links).