RkBlog

Hardware, programming and astronomy tutorials and reviews.

Wielowątkowe aplikacje z QThread

Opis zastosowania klasy QThread do implementacji wątków pobocznych w aplikacji PyQt4

Aplikacja GUI oparta o PyQt4 (czy inne biblioteki) może wykonywać jedną operację na raz. Jeżeli np. zaczniemy pobierać plik z internetu to do czasu jego pobrania aplikacja będzie "zablokowana". Interfejs nie będzie odświeżany i nie będzie reagował na zdarzenia. Tego typu problemy rozwiązuje się wykonując czasochłonne operacje w oddzielnych wątkach. Nawet niezbyt rozbudowana aplikacja może wykorzystywać wiele pobocznych wątków. W PyQt4 poboczne wątki tworzy się za pomocą QtCore.QThread.

Poboczny wątek stworzony za pomocą QThread to klasa dziedzicząca QtCore.QThread i posiadająca metodę run. Oto przykład:

class DoCommandThread(QtCore.QThread):
	def __init__(self, parent, cmd):
		super(DoCommandThread, self).__init__(parent)
		self.ui = parent.ui
		self.parent = parent
		self.cmd = cmd
	def run(self):
		p = Popen(self.cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=os.environ, shell=True)
		#print str(p.communicate()[1])
Powyższa klasa przyjmuje jako argument polecenie do wykonania w powłoce (za pomocą Popen). Odpalenie wątku sprowadza się do trzech linijek umieszczonych np. w metodzie będącej slotem jakiegoś sygnału:
self.du = DoCommandThread(self, 'ls -a')
self.du.start()
QtCore.QObject.connect(self.du,QtCore.SIGNAL("finished()"), self.po_wykonaniu_watku)
Tworzymy obiekt klasy, podajemy argumenty i uruchamiamy wątek za pomocą metody start(). Wątek (QThread) emituje kilka sygnałów jak finished(), gdy wątek zostanie wykonany. Łączymy ten sygnał ze slotem - metodą głównej klasy, która zostanie wykonana po zakończeniu działania wątku. Ważne: poboczne wątki QThread nie mogą wpływać na rysowanie niektórych elementów interfejsu (np. QPixmap).

Poniżej przykład zastosowania pobocznego wątku w aplikacji QYolk do pobrania informacji o aktualizacjach z internetu. W głównej klasie mam takie sloty:

	def stop_check_update_all(self):
		"""
		Przerwanie wątku pobierania aktualizacji
		"""
		self.cua.terminate()
		
		# change the cursor back to normal form
		q = self.cursor()
		q.setShape(QtCore.Qt.ArrowCursor)
		self.setCursor(q)
		self.ui.pkgTree.resizeColumnToContents(1)
	
	def check_update_all(self):
		"""
		Kliknięto w przycisk "Sprawdź aktualizacje"
		Odpalamy wątek
		"""
		# change the cursor untill data gathered:
		q = self.cursor()
		q.setShape(QtCore.Qt.WaitCursor)
		self.setCursor(q)
		
		self.ui.pkgTree.setColumnWidth(1, 300)
		
		# WĄTEK
		self.cua = CheckUpdateAllThread(self)
		self.cua.start()
		QtCore.QObject.connect(self.cua,QtCore.SIGNAL("finished()"), self.check_update_all_finish)
	
	def check_update_all_finish(self):
		"""
		Wątek skończył działanie
		"""
		q = self.cursor()
		q.setShape(QtCore.Qt.ArrowCursor)
		self.setCursor(q)
		self.ui.pkgTree.resizeColumnToContents(1)
A klasa wątku wygląda tak:
class CheckUpdateAllThread(QtCore.QThread):
	def __init__(self, parent):
		super(CheckUpdateAllThread, self).__init__(parent)
		self.ui = parent.ui
		self.parent = parent
	def run(self):
		items = self.ui.pkgTree.findItems('*', QtCore.Qt.MatchWildcard)
		if len(items) > 0:
			ch = pypi.CheeseShop()
			for itm in items:
				pkg = unicode(itm.text(0))
				ver = unicode(itm.text(1))
				try:
					# TO TRWA KILKA SEKUND
					ret = ch.query_versions_pypi(pkg)
					curver = ret[1][0]
				except:
					curver = False
				if ver != curver and curver:
					ms = self.tr('available release')
					msg = u'%s (%s: %s)' % (ver, unicode(ms), curver)
				else:
					ms = self.tr('no updates')
					msg = u'%s (%s)' % (ver, unicode(ms))
				itm.setText(1, msg)

RkBlog

12 July 2009;

Comment article