Anybody who has been into programming for a while will have a story about a horror experience trying to fix a bug in a threaded system. You might even go so far as to say that threads are evil. But then again, what options do we have when our GUI gets the hiccups while making HTTP-requests?
This article describes a simple solution on how to use a single thread to perform your tasks that you otherwise might have started individually. The following example might not be the optimal solution to your problem. As with most programming issues, there is always a better way to do it. I’m a big fan of The Cult of Done Manifesto and point #8 is no exception.
Laugh at perfection. It’s boring and keeps you from being done.
So until I get into trouble, this solution will do. With that said, this solution is simple but it works pretty well. It is at least better than randomly creating threads, like a popcorn factory on fire. I’ve added some images along the way to make the experience of reading this post a little bit more pleasant.
To make this as clear as possible I’d like to give you an example of a problem that this solution solves. Since I’ve been building a Twitter client in python lately I have had some issues with the GUI locking up while fetching tweets and updating statuses. My first solution was to simply start a thread for each task. This worked pretty well but I quickly got problems. While implementing multiple user support into the client problems where tasks were performed in the wrong order occurred. Logging one user in was performed after the timeline was fetched among other things, and I was in thread debugging hell.
To fix the problem I instead created a single thread that fetched tasks from an array. The tasks are popped from the array and performed. The class is described below.
import threading import time from PyQt4.QtCore import QObject, SIGNAL class asyncUiControl(threading.Thread, QObject): mTaskList =  mResultList =  mResultFetched = True def __init__(self): QObject.__init__(self) threading.Thread.__init__(self) def run(self): while not len(self.mTaskList) == 0: while not self.mResultFetched: time.sleep(0.1) if not len(self.mTaskList) == 0: self.__performTask(self.mTaskList.pop()) threading.Thread.__init__(self) def __performTask(self, pTask): self.mResultFetched = False pTask.perform() self.mResultList.append(pTask) self.emit(SIGNAL(pTask.ID)) def addTask(self, pLabelControlTask): self.mTaskList.insert(0, pLabelControlTask) def grabLatestResult(self): vResult = self.mResultList.pop() self.mResultFetched = True return vResult
This shows one of the nice parts with Python. This class was written without me having to write a single interface. I simply needed to make sure each task had a ID that describes the Signal that is supposed to be emitted and a perform() method. The signals that are emitted are Qt specific but you could probably achieve the same result using native python commands.
Before we go deeper into this code lets see how a Task could look! The one below gets the Twitter updates from the list of people the user is following. I’m using the Python Twitter wrapper for the Twitter API.
import twitter class downloadTimeLineTask(): ID = "task_downloadTimeLine()" mUserName = "" mTimeLine =  def __init__(self, pUserName, pApi): self.mUserName = pUserName self._twitterApi = pApi def perform(self): self._twitterApi.SetCacheTimeout(1) self.mTimeLine = self._twitterApi.GetFriendsTimeline(None, 100)
As long as the task has that ID and perform() method it can be customized to you needs. Again, no interface has been used to achieve this.
The ID needs to be connected to a Qt Slot that calls a method. The Slot connection and method is shown below in a sample class.
import AsyncUiControl class someClass(QObject): def __init__(self, pWidget = None): QObject.__init__(self) self.asyncUiControl self.connect(self.asyncUiControl, SIGNAL('task_downloadTimeLine()'),self.sTimeLineDownloaded) def sTimeLineDownloaded(self): vDownloadTimeLineTask = self.asyncUiControl.grabLatestResult()
There are probably hundreds of other ways of doing this but this solution works pretty well for me. One of the main issues I had while creating this was that I didn’t get the task to follow the Signal that was emitted. For some reason Qt does not allow you to send objects that are not Qt objects together with the signal. That is why the “grabLatestResults()” method and resultsFetched loop was needed. The solution should have been much nicer if this was possible. This might work in later versions of Qt so try it out before doing as I did.
This example can also be expanded pretty easily. One thing that might be nice to do next is to give a priority so that they could be executed in an order according to that priority.