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:

  • Prevent GUIs from freezing by using PyQt's QThread
  • Use QRunnable and QThreadPool to create reusable threads.
  • Control interthread communication with slots and signals
  • Employ the best practices for creating GUI apps with PyQt's thread support, and handle shared resources safely.

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:

Explanation:

  • setupUi() function in this Freeze GUI application creates all the graphical elements needed for the GUI. The Call me! button is clicked to make a call. The wording of the Counted label is changed to reflect the number of button clicks using countClicks1().
  • runLongTask() function is called when the Protracted Task! The button is clicked, executing a task that takes five seconds to finish. This is a fictitious work that you coded with the help of time. The caller thread's execution is suspended for the specified number of seconds, secs, by the sleep(secs) function.
  • For the Long-Run Step label to represent the operation's progress, you must additionally call.reportProgress1() in the.runLongTask() function.
  • The application's Interface is updated again five seconds later. Five clicks were made while the GUI was frozen, reflected in the Counted label, which displays ten clicks. The Long-Run Milestone label must accurately depict how far along your Long-Run project is. It skips over the intermediate phases and goes straight from 0 to 5.
  • A stopped main thread causes the application's graphical user interface to freeze. The user's actions take time to respond to by the main thread since it is busy executing a time-consuming activity. When the user is unsure whether the application is functioning properly or has crashed, this behaviour is irritating.
  • Nevertheless, you can use various methods to get around this problem. Doing your lengthy operation via a Workers thread instead of the app's main thread is a typical fix.

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:

  1. The central processing unit (CPU)
  2. the computer language

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:

  • Because of the complexity involved in resource sharing between threads, synchronizing data access, and synchronizing thread execution, multithreaded systems are typically harder to write, maintain, and troubleshoot than single-threaded programmes. This may result in several issues:
  • A race condition occurs when the unpredictable sequencing of events causes the application's behaviour to become non-deterministic. It frequently occurs as a result of two or more threads improperly synchronizing their access to a shared resource. For instance, if both writing and reading operations are carried out in the wrong order, reading and writing storage from different threads may result in a race scenario.
  • Deadlock occurs when threads patiently await the release of a locked resource. For instance, other threads will have to wait endlessly if a resource is locked by a thread and not released after use. Deadlocks can also develop if thread A is awaiting thread B's unlocking of one resource while thread B is awaiting thread A's unlocking another.
  • Livelock is a circumstance in which two or more threads repeatedly react to one other's actions, resulting in an eternity of waiting for both threads. Livelocked threads are unable to continue working on their particular assignment because they are occupied with reacting to other threads. They are neither barred nor dead, though.
  • Starvation occurs when a process cannot obtain the resources required to complete its Task. For instance, if a process cannot get CPU time, it cannot complete its tasks because it is starving for CPU time.

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:

  1. Increasing the speed of your apps by utilizing multi-core processors
  2. Condensing the application's structure into more manageable subtasks
  3. You can keep your application responsive and current by shifting time-consuming operations to Workers' threads.

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:

  1. Primary thread
  2. Threads of Worker

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.

Worker's Threads

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.

  • The advantages of utilizing PyQt's thread support include the following:
  • Classes that deal with threads are completely integrated with PyQt's other infrastructure.
  • The ability to handle events is provided by Workers' threads having their own event loop.
  • Signals and slots can be used to facilitate interthread communication.
  • If you want to communicate with the rest of the library, you should use Python's thread support; otherwise, PyQt's thread support.

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().

  1. You will employ the first strategy in this tutorial, which calls for the actions listed below:
  2. Subclass QObject to create a Workers object, then add your Long-Run Task.
  3. Make a fresh Workers class instance.
  4. Launch a fresh instance of QThread.
  5. Call the newly formed thread to insert the Workers object.
  6. moveToThread(thread).
  7. Join the necessary slots and signals to ensure interthread communication.
  8. Use the QThread object's.start() method.

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:

  • The thread started to signal to the Workers's.runLongTask() slot to ensure that.runLongTask() will be called automatically when the thread starts.
  • After the Workers completes its Task, it sends a finished signal to the threads.quit() slot, quitting the thread.
  • The finished signal instructs the Workers and the thread objects to be deleted using the.deleteLater() slot. Finally, in step 6, you start the thread using .start().

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:

Prevent Freeze GUIs By Using PyQt's QThread

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.

  1. Use the function run() with the Task's code.
  2. To create a runnable task, instantiate any subclass of QRunnable.
  3. Invoke QThreadPool.

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:

  • You subtype QRunnable and reimplement on lines 19 to 28.
  • Put the code you want to run into the run(). In this instance, you simulate a lengthy task using the standard loop. When logging.info() is called, a message is printed on your terminal screen to let you know how the procedure is going.
  • The total quantity of accessible threads is given on line 52. This number is often dependent on the number of cores in your CPU and will vary depending on your particular hardware.
  • You change the label's wording to reflect your ability to operate a certain number of threads on line 53.
  • You begin a for loop that iterates across the available threads on line 55.
  • You create a Runnable object on line 57 and supply the loop variable i.

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:

Prevent Freeze GUIs By Using PyQt's QThread

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:

  • Use a global statement to access balance from within.withdraw(); call.lock() on mutex to acquire the lock and prevent parallel access to the balance; check to see if the account balance permits withdrawing the current amount; call sleep() to simulate that the operation will take some time to complete; and display a message that identifies the person who needs to withdraw some money.
  • Decrease the balance by the needed sum of money; display alerts to indicate whether or not the transaction was approved;
  • Release the lock to allow other threads to access the balance and emit the finished signal to indicate that the operation has been completed. Emit the updatedBalance1 signal to indicate that the balance has been updated.

This application as Output:

Prevent Freeze GUIs By Using PyQt's QThread

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():

  • If there are any, clear the thread in.threads to get rid of any threads that have previously been deleted.
  • Make a dictionary with the names Alice and Bob. Construct a thread for each person; use a list comprehension and.createThread(), and then begin the threads in a loop. Each individual will attempt to withdraw a random quantity of cash from the bank account.

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:

Prevent Freeze GUIs By Using PyQt's QThread
Prevent Freeze GUIs By Using PyQt's QThread

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.

Conclusion

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.

  • Prevent GUI programmes from freezing by using PyQt's QThread, which you learnt how to do in this tutorial.
  • Use PyQt's QRunnable and QThreadPool to build reusable QThread instances.
  • Use PyQt's signals and slots for interthread communication and lock classes to safely use shared resources.
  • With PyQt and its integrated thread support, you also gained certain best practices for multithreaded programming.





Latest Courses