We have all experienced that moment when running some process such as copying files between directories or launching a new instance of an application causes a program to lag for just a moment and, in some cases, to freeze completely. We are then forced to either wait for the current task to complete or Ctrl+Alt+Delete our way to freedom. When you are creating GUIs, you should be aware of how to handle, or more preferably have foresight about avoiding, these situations.
The motivation behind this chapter is twofold – to help you design more robust GUI applications and to inform you of how you might be able to handle situations where your applications need to run long processes. Any action that causes event processing in an application to come to a standstill is bad for a user’s experience.
How to implement threading with QThread
A few other techniques for handling time-consuming processes
The QProgressBar widget for giving visual feedback about a task’s progression
Introduction to Threading
A computer’s performance can be measured by the accuracy, efficiency, and speed at which it can execute program instructions. Modern computers can take advantage of their multicore processors to run those instructions in parallel, thereby increasing the performance of computer applications that have been written to take advantage of the multicore architecture.
The idea of performing tasks in a synchronous manner, that is, where only one task is processed at a time until completion before moving on to the next task, can be inefficient, especially for larger operations. What we need is a way to perform operations concurrently. That is where threads and processes come into play.
Threads and processes are not the same thing. Without going too much into the technical jargon, let’s try and understand the differences between the two. A process is an instance of an application that requires memory and computer resources to run. Opening up the word processor on your computer to write an essay is one process. While writing your essay, you also need to search on the Internet for information. You now have two separate processes running on your computer independently and in parallel. What happens in one process is not influencing the other. Of course, you have multiple tabs open in the web browser, and each tab is loading and updating information; those tabs are working side by side with the web browser. This is where a thread becomes important.
A thread is essential to the concurrency within an individual process. When a process begins, it only has one thread, and multiple threads can be started within a single process. These threads, just like the processes, are managed by the CPU. Multithreading occurs when the CPU can handle multiple threads of execution concurrently within one process. These threads are independent but also share the process’s resources. Using multithreading allows for applications to be more responsive to user’s inputs while other operations are occurring in the background, and to better utilize a system’s resources.
On a system with only a single CPU, true parallelism is actually unachievable. In these instances, the CPU is shared among the processes or threads. To switch between threads, context switches are used to interrupt the current thread, save its state, and then restore the next thread’s state. This gives the user a false appearance of parallelism.
To achieve true parallelism and create a truly concurrent system, a multicore processor would allow threads in a multithreaded application to be assigned to different processors.
Threading in PyQt
Applications based on Qt are event based. When the event loop is started using exec_() , a thread is created. This thread is referred to as the main thread of the GUI. Any events that take place in the main thread, including the GUI itself, run synchronously within the main event loop. To take advantage of threading, we need to create a secondary thread to offload processing operations from the main thread.
PyQt makes communicating between the main thread and secondary threads, also referred to as worker threads, simple with signals and slots. This can be useful for relaying feedback, allowing the user to interrupt a process, and for informing the main thread that a process has finished. Since threads utilize the same address space, they can share data very easily.
However, if multiple threads try to access shared data or resources concurrently, this can cause crashes or memory corruption. Deadlock is another issue that can occur if two threads are blocked because they are waiting for resources. PyQt provides a few classes, for example, QMutex, QReadWriteLock, and QSemaphore, for avoiding these kinds of problems.
Python also has a number of modules for handling threading and processing tasks, including _thread, threading, asyncio, and multiprocessing. While you can also use this modules, PyQt’s QThread and other classes allow you to emit signals between the main and worker threads.
Methods for Processing Long Events in PyQt
While this chapter focuses on using QThread, it is also a good idea to keep in mind that there are also other ways that you might want to try before attempting to use threading in your GUI. Implementing threading can lead to problems with concurrency and identifying errors. Combined with signals and slots, PyQt provides a few different ways to handle time-consuming operations.
- 1.
If there is a process in your application that is causing it to freeze, check to see if that process can be broken down into smaller steps and perform them sequentially. Manually handle the processing of long operations, and explicitly call QApplication.processEvents() to process pending events. This works best if your operations can be processed using a single thread.
- 2.
With QTimer and signals and slots, you can schedule operations to be performed at certain intervals in the future.
- 3.
Use QThread to create a worker thread that will perform long operations in a separate thread. Derive a class from QThread, reimplement run(), and use PyQt’s signal and slot mechanism to communicate with the main thread. This method can help to avoid blocking the main event loop.
- 4.
The QThreadPool and QRunnable classes can be used to divide the work across the CPUs on your computer. Create a subclass of QRunnable and reimplement the run() function; an instance of QRunnable can then be passed to threads that are managed by QThreadPool. QThreadPool handles the queuing and execution of QRunnable instances for you.
There are even other options that may depend upon your application’s requirements. Keep in mind that, while using threads could benefit your application, they could also slow it down or cause errors if used incorrectly.
Project 11.1 – File Renaming GUI
This chapter’s project, shown in Figure 11-1, actually stems from my own experiences. Creating datasets for training neural networks often entails writing Python scripts for labeling thousands of images and data files. Those scripts are generally written to include some kind of visual feedback to the user about how the process is going in the command line.
The QProgressBar Widget
The QProgressBar widget visually relays the progress of an extended operation back to the user. This feedback can also be used as reassurance that a process, such as a download, installation, or file transfer, is still running. Some of the settings that can be controlled include the widget’s orientation and range.
Refer to the project in this chapter for setting up the progress bar.
File Renaming GUI Solution
The GUI window contains various buttons and editor widgets that allow the user to manage file renaming. The user can select a directory using a QFileDialog. They can also enter the new file name in the QLineEdit widget. Using the combo box, they can select the file extension for the files they want to change.
Code for the GUI that renames files in a directory using threading
The application’s GUI can be seen in Figure 11-1.
Explanation
We start with importing Python and PyQt classes. The style sheet is used to modify the appearance of the QProgressBar.
Let’s start by looking at the RenameFileGUI class . Here we set up the window and other widgets, including push buttons for selecting the directory and starting the process for renaming files, line edit widgets, and the text edit and the progress bar widgets for relaying feedback.
The user can select a directory using QFileDialog . Once a directory is chosen, the user can enter the new file names into change_name_edit and select the file extension for the types of files to change in the combo box.
Renaming the files could take place in the main thread. This wouldn’t be a problem for a few files. However, if the user wants to work with a large number of files, this would cause the GUI to be locked until the operations are finished. Therefore, the process for renaming the files, along with updating the progress bar and the text edit widgets, is performed in the worker thread.
The reimplemented QThread method run() begins executing the thread. The time-consuming operations – traversing the directory, renaming files, and emitting the signals for updating the QProgressBar and QTextEdit – are performed in run(). However, this method is not called directly. The QThread method start() is used to communicate with the worker thread and begin executing the thread by calling run(). The start() method is called in renameFiles().
Summary
Preventing GUIs from becoming frozen while processing long operations is important for a user’s experience. There are a few options for effectively handling blocking in your application, including using timers and threads. PyQt makes using threading seem relatively simple with QThread and the signal and slot mechanism. However, you must be careful when using QThread to ensure that threads protect access to their own data. While not displayed in this chapter’s short project, QThread also has methods, such as started(), finished(), wait(), and quit(), for managing threads.
In Chapter 12, we will take a look at an array of projects that utilize different PyQt classes.