Prevent Freeze GUIs By Using PyQt's QThread
The event loop and GUI run on the main thread of execution in PyQt graphical user interface (GUI) programs. Your GUI will become unresponsive if you start a Long-Run process in this thread because it will only finish once it does. The user experience will be poor because they can only interact with the programme during that period. Luckily, you can get around this problem using PyQt's QThread class.
This tutorial will teach how to:
Long-Run GUI Freezing Tasks
A prevalent problem in GUI programming, Long-Run tasks dominating the main thread and causing the software to freeze nearly invariably lead to a poor user experience.
Let's say you want the total amount of times you clicked on the Click me! Button to be shown in the Counted label. A task that takes a long time to complete will start when you click the Long-Run Task! Button. Your resource-intensive process could be a file downloading, a query to a sizable database, or any other lengthy task.
Using PyQt and then a single thread of operation, the following is a first attempt at programming this application:
You'll discover how to use PyQt's integrated thread support to resolve the issue of unresponsive or stopped GUIs and offer the greatest user experience in your apps in the sections below.
Multithreading: The Basics
Your programs may occasionally be broken up into several smaller jobs or subprograms that you can execute in different threads. Avoiding application freezing while carrying out time-consuming operations could speed up your programmes or assist you in improving user experience.
A thread is a unique execution flow. A thread is part of a process in the majority of operating systems, and processes can have numerous threads running simultaneously. An instance of a program or application that would be currently operating in a particular computer system is represented by each process.
The number of threads is unlimited. The difficult part is figuring out how many threads to employ. The system resources constrain the number of threads you can use when using I/O-bound threads at your disposal. On the other hand, having several threads equal to or less than the number of CPU cores in your system will be advantageous if you're working with CPU-bound threads.
Multithreaded programming is the process of creating programmes that can execute numerous tasks simultaneously on various threads. Using this method, multiple jobs should ideally operate concurrently and separately. This isn't always doable, though. Software may be unable to operate numerous threads concurrently due to at least two factors:
You cannot, for instance, execute numerous threads simultaneously on a computer with a single core of processing power. To imitate parallel thread execution, certain single-core Processors, on the other hand, let the operating system divide the processing time among several threads. This gives the impression that your threads are operating concurrently while they are only running one at a time.
On the other hand, you can execute numerous threads concurrently if you have a computer with multiple cores or a computer cluster. Your programming language has a significant role in this situation. Several underlying programming language structures restrict the execution of multiple concurrent threads.
In these circumstances, threads run concurrently for few reasons like:
You must take care to safeguard your resources from concurrent writing or state modification access while developing multithreaded programmes. Put another way, and you must stop many threads from simultaneously using a certain resource.
Multithreaded programming offers advantages to a variety of applications in at least three different ways:
Threads are not executed simultaneously in CPython, the C version of the Python language. A global interpreter lock (GIL) in CPython effectively prevents more than one Python thread from running concurrently.
Due to the overhead caused by context switching between threads, this can have a severe impact on the performance of Python programs that employ threads.
Multithreading in PyQt with QThread
PyQt, a subset of Qt, offers its framework for building QThread-based multithreaded applications. Applications built with PyQt can use two different types of threads:
The main thread of the application is always active. The application and its Interface are run from here. On the other hand, the necessity for processing by the application determines whether or not there are Worker threads. For instance, if your application frequently performs time-consuming heavy activities, you should have Workers threads to handle such tasks and prevent the application's GUI from freezing.
The Main Thread
Because it manages all widgets and other GUI elements, the primary thread of execution in PyQt programmes is also called the GUI thread. When you launch the programme in Python, this thread is created. Following a call to.exec() on the QApplication object, the event loop for the application is executed in this thread. This thread manages your windows, dialogue boxes, and host operating system interactions.
Asynchronously, or one task after another, is the default behaviour for each event or activity that occurs on the application's main thread, including user actions on the GUI. As a result, if you start a lengthy process in the main thread, the application must wait for it to complete, which causes the GUI to become unresponsive.
You must construct and update all of your widgets in the GUI thread, which is vital to remember. But, you can perform additional time-consuming processes in Workers threads and use their Output to supply your application's GUI components. This implies that GUI elements will operate as information consumers, consuming data from the threads doing the actual work.
Your PyQt applications can have as many Workers threads as necessary. Workers threads are auxiliary execution threads that can delegate time-consuming activities from the main thread and avoid GUI freeze. QThread enables the creation of Workers' threads.
With the ability to connect with the main thread using PyQt's signals and slots system, each Worker's post can have its event loop. An object is considered to belong to or have an affinity for a thread if it is created in that thread from any class that derives from QObject. Its offspring must also be connected to that thread.
QThread isn't a thread. A thread from the operating system is wrapped in it. When you use QThread, the actual thread object is produced. start().
QThread offers a high-level programming interface for applications (API) to manage threads. The thread starts and ends are signalled via this API's inclusion of signals.started() and.finished(). It also contains slots and methods like.start(),.wait(), and.exit ().
QThread vs threading of Python
The threading module of the Python standard library provides a dependable and consistent approach for working with threads in Python. A high-level Python API is provided by this module for multithreaded programming.
Typically, threading is used in Python programs. You do have another choice, though, if you're utilizing PyQt to create GUI applications with Python. A complete, integrated, greater API for multithreading is provided by PyQt1.
Should I use PyQt's thread support or Python's thread support in my PyQt applications? It depends, is the response.
Python's threads, for instance, make more sense if you're developing a GUI application that will include a web version because your back end won't need to handle threads.
Using QThread to Prevent Freeze GUIs
Offloading time-consuming activities to worker threads in a GUI application is a typical practice to keep the GUI responsive to user interactions. To generate and manage Workers threads in PyQt, use QThread.
A parallel event loop is provided by instantiating QThread. An event loop enables thread-owned objects to receive signals on their slots, with the thread executing them.
Contrarily, subclassing QThread enables the execution of parallel code devoid of an event loop. With this strategy, you can make an event loop by explicitly invoking exec().
By following these steps, you may convert your Frozen GUI application into a Responsive GUI application:
Explanation: To start with, you do a few required imports. Then you follow the steps you saw earlier. Workers are created as a QObject subclass in step 1. In Workers, you can create finished and progress signals. Take note that signals must be created as class attributes.
Additionally, you create a method called.runLongTask() in which you place all of the code necessary to carry out your Long-Run Task. In this example, a for loop that iterates five times with a one-second delay between each iteration is used to simulate a Long-Run task. The progress signal, which indicates how far along the operation is, is also sent out by the loop. The finished signal is then sent out by.runLongTask() to show that the processing is finished.
You create a Workers instance and a QThread instance in steps 2 to 4, serving as this Task's workspace. You move your specialist object to the string by calling .moveToThread() on the Worker, involving the string as a contention.
Connect the slots and signals listed below in step 5:
Once the thread is active, you must perform a few resets to get the application to function consistently. To prevent a user from clicking the Long-Run Task! Button while the Task is in progress, you can disable it. Additionally, you link the thread's finished signal to a lambda function that, when called, activates the Long-Run Task! Button. The Long-Run Step label's text is reset upon your final connection.
After you launch this programme, the following window will appear on your screen:
QRunnable and QThreadPool: Reusing Threads
You will experience a substantial overhead associated with creating and terminating threads if your GUI apps significantly rely on multithreading. So that your applications continue to run efficiently, you'll also need to think about how many threads you can launch on a particular machine. Thankfully, PyQt's thread support also offers you a solution to these problems.
There is a global threading pool for each programme. A reference to it can be obtained by calling QThreadPool.globalInstance ().
Although it's common to use the default thread pool, QThreadPool, which offers a collection of disposable threads, allows you to construct your thread pool.
A proposed number of threads is maintained and managed by the global thread pool, typically based on the number of cores in your present CPU. It also takes care of the Task queuing and execution for the threads in your application. Because the threads inside the pool are reusable, there is no longer any overhead from creating and deleting threads.
You use QRunnable to construct tasks and execute them in a thread pool. This class symbolizes a process or line of code that must be executed. There are three processes involved in generating and carrying out runnable tasks:
Reimplement QRunnable by subclassing it.
With the binary compatible Task as a parameter, call start().
The necessary code for the Task must be run(). When you call, your Task is started in one of the pool's available threads. start(). The job is added to the pool's run queue by.start() if there isn't a thread available. The code in.run() is performed in the available thread.
Below is a GUI program that demonstrates how to incorporate this procedure into your code:
Here's how the code functions:
It's significant to note that this tutorial includes certain logging-related examples. Using info() with a simple configuration, messages are printed on the screen. This is necessary since print() is not a thread-safe function and could result in a mess in your Output. Logging routines are thread-safe, allowing you to use them in multithreaded applications.
If you use this application, you'll observe the behaviour described below:
Up to four threads can be started by the application when you click the Click me! Button. The program updates the background terminal with each thread's progress. Even when you close the application, the threads keep operating until they complete their responsibilities.
With Python, no method exists to halt a QRunnable instance from the outside. To get around this, you can make a global Boolean variable and repeatedly check it inside your QRunnable subclasses to end them when the value turns True. Because QRunnable needs more support for signals and slots, interthread communication can be difficult when utilizing QThreadPool and QRunnable.
Nevertheless, QThreadPool manages a thread pool automatically and takes care of the queuing and implementation of runnable tasks.
Workers QThreads Communication
You may need to facilitate communications between the main thread of your application and your Worker's threads if you're using PyQt to programme many threads. Doing so allows you to pass data to your threads, get feedback on the state of the Worker's threads, update the GUI appropriately, allow users to pause the execution, and more.
A reliable and secure method of communicating with Workers' threads in a GUI application is provided by PyQt's signals and slots technology.
Conversely, you can also need to establish communication amongst Worker's threads, for example, by sharing data buffers or any other kind of resource. In this situation, you must safeguard your data and resources against simultaneous access.
Using Slots and Signals
An object that can be accessed simultaneously by multiple threads and is guaranteed to be in a valid state is a thread-safe object. Because PyQt's slots and signals are thread-safe, you can use them to share data between threads and establish interthread communication.
Signals from one thread can be connected to slots in another thread. As a result, you can respond to a signal emitted in one thread or another by running code in a different thread. A secure means of communicating between threads are created as a result of this.
Because signals can also contain data, if you send out a signal that contains data, you will receive that data in every slot connected to the signal.
In the Responsive GUI application model, you utilized the signs and spaces component to lay out the correspondence between strings. For instance, you connected the Workers' progress signal to the.reportProgress1() slot of the application. To update the Long-Run Step label,.reportProgress1() takes an integer value from progress, which indicates the progress of the Long-Run Task.
PyQt's interthread communication is built on the foundation of connecting signals and slots in various threads. At this point, try using signals and slots to display the operation's progress in the Responsive GUI application using a QToolBar object rather than the Long-Run Step label.
QMutex (Protecting Shared Data)
In multithreaded PyQt applications, QMutex is frequently used to prevent multiple threads from accessing shared resources and data concurrently. You will write the code for a graphical user interface (GUI) that uses a QMutex object to prevent concurrent write access to a global variable.
You will code an example that manages a bank account from which two people can withdraw money anytime to learn how to use QMutex. In this instance, you need to prevent parallel access to the account balance. In any other case, people may withdraw more money than they have in the bank.
Take, for instance, the scenario where you have a $100 account. When two people simultaneously check the available balance, they discover that the account has $100. They proceeded with the transaction because they believed they could withdraw $60 and keep $40 in the account. The account will have a deficit of $20, which could be a significant issue.
The first step in coding the example is to import the necessary classes, functions, and modules. You also define two global variables and add a basic logging configuration:
You'll utilize the global variable balance to keep track of the account's current balance. You'll utilize the QMutex object mutex to defend balance against concurrent access. In other words, a mutex will stop multiple threads from simultaneously accessing balance.
The next stage is to develop a subclass of QObject that houses the logic for controlling bank account withdrawals. That class will be known as AccountManager.
You then define.withdraw(). You perform the following with this technique:
This application as Output:
Code for creating the above GUI:
Every time a withdrawal is made, the balance of the account is deducted by the required sum. With this technique, the Current Balance label's text is updated to reflect changes to the account balance. You must create the two persons and launch a thread for each of them in order to complete the application:
To begin with, you add the instance attribute.threads to your Window's initializer. To keep the threads from accidentally stepping beyond their scope, this variable will store a list of them. return from startThreads(). To generate two persons and a thread for each, you define.startThreads().
You do the following actions in.startThreads():
You're almost done with this last bit of code.
If you run this application from your command line, then you'll get the following behaviour:
The threads are functional, as the Output on the background terminal shows. In this example, you may safeguard and synchronize access to the bank account balance by using a QMutex object. Users are thus prevented from withdrawing more money than is currently in their account.
Long-Run activities executing on a PyQt application's main thread may make the GUI unusable and freezing. This is a problem that frequently arises in Functional requirements and coding and can make for a poor user experience. This problem is easily solved in your Graphical applications by offloading Long-Run activities using Workers threads created using PyQt's QThread.