본문 바로가기
Qt5 Python GUI Programming Cookbook

Doing Asynchronous Programming in Python

by 자동매매 2023. 2. 28.

In this chapter, we will learn to use asynchronous programming in Python, which is how more than one task can be executed in parallel. We will be covering the following topics:

  • Updating a progress bar using a thread
  • Updating two progress bars using two threads
  • Updating progress bars using threads bound with a locking mechanism
  • Updating progress bars simultaneously using asynchronous operations
  • Managing resources using context manager

Introduction

Threads are used for running several programs concurrently in a single process, so they help in implementing multitasking. Threads, once created, execute simultaneously and independently of each other. Threads are basically small processes that are created for executing certain tasks independently. Threads can be pre-empted, that is, interrupted or stopped temporarily by setting them in sleep mode and then resuming execution.

To work with threads in Python, we will be making use of its threading module. The threading module provides several methods that provide information on currently active threads. A few of the methods provided by the threading module are as follows:

  • threading.activeCount() active thread objects
  • threading.currentThread()
  • threading.enumerate() thread objects
  • This method returns the number of currently
  • This method returns the current thread object
  • This method returns a list of all currently active

Besides the preceding methods, the threading module has the Thread class, which implements threading. The following are the methods provided by the Thread class:

  • run(): This method begins the execution of a thread
  • start(): This method starts a thread by calling the run method
  • join([time]): This method waits for threads to terminate
  • isAlive(): This method checks whether a thread is still executing
  • getName(): This method returns the name of a thread
  • setName(): This method sets the name of a thread

Multithreading

In multithreading, more than one thread runs simultaneously. Consequently, computation and other jobs are performed quicker. This is because the time that is usually wasted in waiting for I/O operations is used by another thread to perform its task.

When two or more threads run concurrently, there may be an ambiguous situation where two or more threads try to run a block of statements together. In such a situation, we need to implement a mechanism that makes a thread wait until the current thread finishes its processing on a block of statements. When the current thread finishes its processing, only then is another thread allowed to process those statements. Such a mechanism is called synchronization of threads. Locking is a popular technique used in synchronization of threads. When a thread wants to execute a block of shared statements, it acquires a lock. No thread can execute statements whose lock is acquired by another thread, hence such threads are asked to wait to get those statements unlocked. The thread that acquired the lock processes those statements, and when finished, releases the lock. The waiting thread can then acquire the lock on that block of statements and begin its execution.

Asynchronous programming

Asynchronous programming makes processing a lot faster as more than one task executes in parallel to the main thread. A Python library that is very commonly used in asynchronous programming is asyncio. For asynchronous programming, you need an event loop that schedules asynchronous tasks, that is, the event loop picks up a task that is waiting in a queue for processing. Functions that need to be run asynchronously need to be prefixed with the async keyword.

The functions that will be executed asynchronously are marked with the async keyword. The event loop recognizes the asynchronous function through the async prefix. All asynchronous functions are known as coroutine. Any asynchronous function (coroutine) after starting a task, continues its task until either the task is over or it is asked to wait by the await call. The asynchronous function will suspend its execution if the await call occurs. The period of suspension is determined by the await asyncio.sleep(delay) method.
The function will go to sleep for the specified delay period.
The following are the methods that are required in asynchronous programming:

  • asyncio.get_event_loop(): This method is used to get the default event loop (). The event loop schedules and runs asynchronous tasks.
  • loop.run_until_complete(): This method won't return until all of the asynchronous tasks are done.

Updating progress bar using thread

Progress bars are actively used in applications to indicate that a task is working in the background. Threads also do the same; threads too work in the background and do their assigned task. Let's see how the progress bar and threads can be linked.

In this recipe, we will be displaying a progress bar that is being updated using threads. The value in the progress bar will be updated through a running thread.

 

demoProgressBarThread.py
0.00MB
demoProgressBarThread.ui
0.00MB

 

 

 

How to do it...

To associate a progress bar with a thread and to update the progress bar interactively via the thread, use the following steps:

  1. Let's create an application based on the Dialog without Buttons template.
  2. Add QLabel and QProgressBar widgets to the form by dragging and dropping the Label and Progress Bar widgets onto the form.
  3. Set the text property of the Label widget to Downloading the file.
  4. Let the objectName property of the Progress Bar widget be the default, progressBar.
  5. Save the application as demoProgressBarThread.ui. The form will now appear as shown in the following screenshot:

The user interface created with Qt Designer is stored in a .ui file, which is an XML file and needs to be converted into Python code. The generated Python script, demoProgressBarThread.py, can be seen in the source code bundle of the book.

  1. Treat the demoProgressBarThread.py script as a header file, and import it into the file from which you will invoke its user interface design.
  2. Create another Python file with the name callProgressBar.pyw and import the demoProgressBarThread.py code into it:
import sys
import threading
import time
from PyQt5.QtWidgets import QDialog, QApplication
from demoProgressBarThread import *
class MyForm(QDialog):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.show()
class myThread (threading.Thread):
    counter=0
    def __init__(self, w):
        threading.Thread.__init__(self)
        self.w=w
        self.counter=0
    def run(self):
        print ("Starting " + self.name)
        while self.counter <=100:
            time.sleep(1)
            w.ui.progressBar.setValue(self.counter)
            self.counter+=10
            print ("Exiting " + self.name)
            
if __name__=="__main__":
    app = QApplication(sys.argv)
    w = MyForm()
    thread1 = myThread(w)
    thread1.start()
    w.exec()
    sys.exit(app.exec_())

 

How it works...

In the callProgressBarThread.pyw file, there are two classes: one is the main class, called the MyForm class, which basically interacts with the GUI form, and the second class is the myThread class, which creates and invokes the thread to update the progress bar made in the GUI form.

To use threads in Python, the first step is to import Thread. The import

threading statement imports Thread in the current script. After importing Thread, the second step is to subclass our class from the Thread class. Hence, our class called myThread inherits the Thread class.

In the main section of the script, an object of the main class, MyForm, is defined by name w. Then, an object of the myThread class is defined by name, thread1. The progress bar which is invoked in the main class, MyForm, has to be updated through the thread, hence the object of the main class, w, is passed as a parameter while creating the thread object, thread1. On invoking the start method on the thread object, thread1, the run method defined in the myThread class will be invoked. In the run method, a counter is set to run from value 0% to 100% with a delay of 1 second between every increment in the counter. The value of the counter will be used to display progress in the progress bar. Hence, the progress bar will progress from 0 to 100 with a delay of 1 second in between each percentage.

On running the application, you will find the progress bar progressing from 0% to 100%, as shown in the following screenshot:

Updating two progress bars using two threads

For multitasking, you need more than one thread running simultaneously. The focus of this recipe is on understanding how two tasks can be performed asynchronously via two threads, that is, how CPU time is allocated to these two threads and how switching is done between them.

This recipe will help you understand how two threads run independently without interfering with each other. We will be making use of two progress bars in this recipe. One progress bar will represent progress in downloading a file, and the other progress bar will represent progress in scanning for viruses on the current drive. Both progress bars will progress independently of each other through two different threads.

 

demoTwoProgressBars.py
0.00MB
demoTwoProgressBars.ui
0.00MB

 

 

 

How to do it...

Let's learn how two progress bars are managed by two threads. To understand how CPU time is allocated to each running thread to execute two tasks simultaneously, perform the following steps:

  1. Let's create an application based on the Dialog without Buttons template. We need two pair of QLabel and QProgressBar widgets in this application.
  2. Add a QLabel and a QProgressBar widget to the form by dragging and dropping a Label widget onto the form and, below the Label widget, drag and drop a progress bar on the form.
  3. Repeat the procedure for another pair of Label and Progress Bar widgets.
  4. Set the text property of the first Label widget to Downloading the file.
  5. Set the text property of the second Label widget to Scanning for Virus.
  6. Set the objectName property of the first progress bar to progressBarFileDownload.
  7. Set the objectName property of the second progress bar to progressBarVirusScan.
  8. Save the application as demoTwoProgressBars.ui. After performing the preceding steps, the form will now appear as shown in the following screenshot:

The user interface created with Qt Designer is stored in a .ui file, which is an XML file. By applying the pyuic5 utility, the XML file can be converted into Python code. You can find the generated Python script, demoTwoProgressBars.py, in the source code bundle for the book.

  1. Treat the demoTwoProgressBars.py script as a header file, and import it into the file from which you will invoke its user interface design.
  2. Create another Python file with the name callProgressBarTwoThreads.pyw and import the demoTwoProgressBars.py code into it:
import sys
import threading
import time
from PyQt5.QtWidgets import QDialog, QApplication
from demoTwoProgressBars import *
class MyForm(QDialog):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.show()
class myThread (threading.Thread):
    counter=0
    def __init__(self, w, ProgressBar):
        threading.Thread.__init__(self)
        self.w=w
        self.counter=0
        self.progreassBar=ProgressBar
    def run(self):
        print ("Starting " + self.name+"n")
        while self.counter <=100:
            time.sleep(1)
            self.progreassBar.setValue(self.counter)
            self.counter+=10
            print ("Exiting " + self.name+"n")
if __name__=="__main__":
    app = QApplication(sys.argv)
    w = MyForm()
    thread1 = myThread(w, w.ui.progressBarFileDownload)
    thread2 = myThread(w, w.ui.progressBarVirusScan)
    thread1.start()
    thread2.start()
    w.exec()
    thread1.join()
    thread2.join()
    sys.exit(app.exec_())

 

How it works...

In the callProgressBarTwoThreads.pyw file, there are two classes: one is the main class, called the MyForm class, which basically interacts with the GUI form, and the second class is the myThread class, which creates and invokes two threads, which in turn update the two Progress Bar widgets used in the GUI. Recall, the two progress bars are defined in the GUI to represent progress in downloading a file and scanning for viruses.

When using threads in Python, the first step is to import Thread. The import threading statement imports Thread in the current script. After importing Thread, the second step is to subclass our class from the Thread class. Hence, our class called myThread inherits the Thread class.

In the main section of the script, an object of the main class, MyForm, is made, called w. Thereafter, two threads are created by name, thread1 and thread2, by creating two instances of the myThread class. Because thread1 is supposed to update the progress bar
that represents progress in file downloading, while creating it two parameters are passed to it: the first is the instance of the main class, MyForm, and the second parameter is the progress bar with the object name progressBarFileDownload.

The second thread, thread2, will update the progress bar that represents virus scanning, so while creating the thread2 instance, two parameters are passed: the first is the MyForm class instance, w, and the second parameter is ProgressBar with the object name progressBarVirusScan.

On invoking the start method on the thread object, thread1, the run() method defined in the myThread class will be invoked. In the run() method, a counter is set to run from 0% to 100% with a delay of 1 second between every increment in the counter. The value of counter will be used to display progress in the progress bar . Hence, the progress bar with the object name progressBarFileDownload will progress from 0 to 100 with a delay of 1 second in between each percentage.

Similarly, when the start() method is invoked on the thread2 object, its run() method will be invoked. Remember, the run methods of both threads will run independently, without interfering with each other. The run() method of thread2 will make the progress bar with the object name progressBarVirusScan progress from 0% to 100% with a delay of 1 second between each increment in value.

On running the application, you will find the two progress bars progressing from 0% to 100%, independently of each other. The thread will automatically stop when the associated progress bar reaches 100%, as shown in the following screenshot:

In order to control access to shared resources, a locking mechanism is applied to threads: the Lock object is used to prevent two threads from accessing the same resource simultaneously.

To work on any resource, a thread is compelled to acquire a lock on that resource first. Only one thread at a time can acquire a lock on a resource. If a resource is locked, that is, it is being used by some other thread, no other thread can access or perform tasks on that resource until the current thread finishes its task on that resource and unlocks the resource, that is, all other threads need to wait until the resource is unlocked. A lock can be in one of two states, "locked" or "unlocked". Initially, a lock is in the unlocked state and the moment a thread needs to access a resource, it acquires a lock and turns that lock into the "locked" state, informing the other threads that the resource is in use.

To acquire and release the locks, a thread can use the following two basic methods:

  • acquire(): This method is invoked by a thread to inform other threads that it needs to work on a resource and needs to get a lock on it. If the resource is already in a locked state, then this method will block the invoking thread. Only when the resource becomes free will the blocked thread be unblocked, signaling that the resource that it was waiting for is free now and can be locked by it. A resource becomes free when a thread that is using it invokes the release() method, which indicates that the resource is now in an unlocked state and the waiting thread is welcome to lock it.
  • release(): As the name suggests, this method is invoked by a thread that has locked a resource and has finished its tasks on that thread. By invoking the release() method, the resource gets unlocked and can be acquired by any waiting thread. This method should only be called when the resource is in the locked state, otherwise, this method will result in an error.

 

Updating progress bars using threads bound with a locking mechanism

This recipe will help you understand how two threads can avoid ambiguity by making use of locks. That is how shared resources can be accessed and manipulated by two threads simultaneously, without giving ambiguous results.

We will be making use of two progress bars in this recipe. One progress bar will represent progress in downloading a file, and the other progress bar will represent progress in scanning for viruses on the current drive. Only one progress bar will progress at a time.

 

 

How to do it...

The following steps will help you understand how two threads can run simultaneously, updating a common shareable resource, without giving ambiguous results:

  1. Let's create an application based on the Dialog without Buttons template. We need two pair of QLabel and QProgressBar widgets in this application.
  2. Add a QLabel and a QProgressBar widget to the form by dragging and dropping a Label widget on the form and, below the Label widget, drag and drop a Progress Bar widget on the form.
  3. Repeat the procedure for another pair of Label and Progress Bar widgets.
  4. Set the text property of the first Label widget to Downloading the file and the second Label widget to Scanning for Virus.
  5. Set the objectName property of the first Progress Bar widget to progressBarFileDownload.
  6. Set the objectName property of the second Progress Bar widget to progressBarVirusScan.
  7. Save the application as demoTwoProgressBars. The form will now appear as shown in the following screenshot:

The user interface created with Qt Designer is stored in a .ui file, which is an XML file, and needs to convert into Python code. The pyuic5 command is used for converting the XML file into a Python script. You can find the generated Python script, demoTwoProgressBars.py, in the source code bundle for this book.

  1. Treat the demoTwoProgressBars.py file as a header file, and import it into the file from which you will invoke its user interface design.
  2. Create another Python file with the name callProgressBarTwoThreadsLocks.pyw and import the demoTwoProgressBars.py code into it:
import sys
import threading
import time
from PyQt5.QtWidgets import QDialog, QApplication
from demoTwoProgressBars import *
class MyForm(QDialog):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.show()
class myThread (threading.Thread):
    counter=0
    def __init__(self, w, ProgressBar):
        threading.Thread.__init__(self)
        self.w=w
        self.counter=0
        self.progreassBar=ProgressBar
    def run(self):
        print ("Starting " + self.name+"n")
        threadLock.acquire()
        while self.counter <=100:
            time.sleep(1)
            self.progreassBar.setValue(self.counter)
            self.counter+=10
            threadLock.release()
            print ("Exiting " + self.name+"n")
            
if __name__=="__main__":
    app = QApplication(sys.argv)
    w = MyForm()
    thread1 = myThread(w, w.ui.progressBarFileDownload)
    thread2 = myThread(w, w.ui.progressBarVirusScan)
    threadLock = threading.Lock()
    threads = []
    thread1.start()
    thread2.start()
    w.exec()
    threads.append(thread1)
    threads.append(thread2)
    for t in threads:
        t.join()
        sys.exit(app.exec_())

How it works...

In the callProgressBarTwoThreadsLocks.pyw file, there are two classes: one is the main class, called the MyForm class, which basically interacts with the GUI form, and the second class is the myThread class, which creates and invokes two threads, which in turn update the two Progress Bar widgets used in the GUI.

To use threads in Python, the first step is to import Thread. The import threading statement imports Thread in the current script. After importing Thread, the second step is to subclass our class from the Thread class. Hence, our class called myThread inherits the Thread class.

In the main section of the script, an object of the main class, MyForm, is made, called w. Thereafter, two threads are created by name, thread1 and thread2, by creating twoinstances of the myThread class. Because thread1 is supposed to update the progress bar
that represents progress in file downloading, while creating it two parameters are passed to it. The first is the instance of the main class, MyForm, and the second parameter isthe ProgressBar with the object name progressBarFileDownload.

The second thread, thread2, will update the progress bar that represents virus scanning, so while creating the thread2 instance, two parameters are passed. The first is the MyForm class instance, w, and the second parameter is the ProgressBar with the object
name progressBarVirusScan.

On invoking the start method on the thread object thread1, the run method defined in the myThread class will be invoked.

In the run method, the thread1 object acquires the lock by invoking the acquire method. Consequently, the while block will execute completely for this thread1 object only. That is, until the release method is called by thread1, the while loop for thread2 will not run.

In other words, the progress bar with the object name progressBarFileDownload, which is being updated by thread1, will progress alone from 0 to 100. Once the progress bar from thread1 reaches 100, the release method is invoked by thread1. The thread2
object will execute its run method on release of the lock by thread1.

The run method of thread2 also acquires the lock so that no other thread can run this block of code until thread2 releases the lock. The run method makes the progress bar with the object name progressBarVirusScan progress from 0 to 100 with a delay of 1 second between each increment in value.

On running the application, you will find that the first progress bar, which represents file downloading, progresses from 0% till 100%, whereas the other progress bar is still at0% (see the left side of the next screenshot). When the first progress bar reaches 100%, meaning when the first thread releases the lock, the second thread will start its job and hence the second progress bar will begin progressing from 0% to 100% (see the right side of the screenshot):

Updating progress bars simultaneously using asynchronous operations

This recipe will help you understand how asynchronous operations are performed in Python. asyncio is a library in Python that supports asynchronous programming. Asynchronous means, that besides the main thread, one or more tasks will also execute in parallel. While using asyncio, you should remember that only code written in methods flagged as async can call any code in an asynchronous way. Besides this, async code can only run inside an event loop. The event loop is the code that implements multitasking. It also means that to perform asynchronous programming in Python, we need to either create an event loop or get the current thread's default event loop object.

We will be making use of two progress bars and both will be updated simultaneously via asynchronous operations.

demoTwoProgressBarsAsync.py
0.00MB
demoTwoProgressBarsAsync.ui
0.00MB

How to do it...

Perform the following steps to understand how asynchronous operations are performed:

  1. Let's create an application based on the Dialog without Buttons template. We will require two pair of QLabel and QProgressBar widgets in this application.
  2. Add a QLabel and a QProgressBar widget to the form by dragging and dropping a Label widget on the form and, below the Label widget, drag and drop a Progress Bar widget on the form.
  3. Repeat the procedure for another pair of Label and Progress Bar widgets.
  4. Above the Label and Progress Bar pair, drag and drop a push button on the form.
  5. Set the text property of the push button to Start.
  6. Set the text property of the first Label widget to Downloading the file and the second Label widget to Scanning for Virus.
  7. Set the objectName property of the push button to pushButtonStart.
  8. Set the objectName property of the first Progress Bar widget to progressBarFileDownload and that of the second Progress Bar widget to progressBarVirusScan.
  9. Save the application as demoTwoProgressBarsAsync.ui. The form will appear as shown in the following screenshot:

The user interface created with Qt Designer is stored in a .ui file, which is an XML file and needs to convert into the Python code. The pyuic5 command is used for converting the XML file into the Python code. The generated Python script, demoTwoProgressBarsAsync.py, can be seen in the source code bundle for this book.

  1. Treat the demoTwoProgressBarsAsync.py script as a header file, and import it into the file from which you will invoke its user interface design.
  2. Create another Python file with the name callProgressBarAsync1.pyw and import the demoTwoProgressBarsAsync.py code into it:

수정필요>>>

import sys, time
import asyncio
from PyQt5.QtWidgets import QDialog, QApplication
from quamash import QEventLoop
from demoTwoProgressBarsAsync import *
class MyForm(QDialog):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.ui.pushButtonStart.clicked.connect(self.invokeAsync)
        self.show()
    def invokeAsync(self):
        asyncio.ensure_future(self.updt(0.5, self.ui.progressBarFileDownload))
        asyncio.ensure_future(self.updt(1, self.ui.progressBarVirusScan))
    @staticmethod
    async def updt(delay, ProgressBar):
        for i in range(101):
            await asyncio.sleep(delay)
            ProgressBar.setValue(i)

    def stopper(loop):
        loop.stop()
        
if __name__=="__main__":
    app = QApplication(sys.argv)
    loop = QEventLoop(app)
    asyncio.set_event_loop(loop)
    w = MyForm()
    w.exec()
    with loop:
        loop.run_forever()
        loop.close()
    sys.exit(app.exec_())

How it works...

In the callProgressBarAsync1.pyw file, in the main section, an object of the QEventLoop class is made called loop. For asynchronous programming, we use event loops. Why?

In asynchronous programming, there might be more than one task in a queue waiting for the CPU's attention. The event loop picks up a task from the queue and processes it. These tasks that are picked up from the queue are also known as coroutines. After getting picked up, the event loop is executed forever; that is, it will see whether there are any tasks in the queue to be executed. If any are found, the task is executed. Following the current task, the next in the queue is picked up, and so on.

The clicked() signal of the push button is connected with the invokeAsync() method. Whenever the push button is clicked, it will invoke the invokeAsync() method.

In the invokeAsync() method, the asyncio.ensure_future method is called, scheduling the execution of a coroutine object in the future. The asyncio.ensure_future method is called twice.

When the asyncio.ensure_future method is called for the first time, it invokes the updt static method and passes two parameters: one is a time delay of 0.5 seconds and the second parameter is the progress bar with the object name progressBarFileDownload.

In the second call to the asyncio.ensure_future method, it again invokes the updt static method and passes two parameters: one is the time delay of 15 seconds and the second parameter is the progress bar with the object name progressBarVirusScan.

In the updt static method, the progress bar that is supplied as a parameter is updated from 0 to 100. The progress bar is updated after the supplied delay. That is, the progress bar with the object name progressBarFileDownload is updated from 0 to 100 with a delay of 0.5 seconds. Similarly, the progress bar with the object name progressBarVirusScan is updated from 0 to 100 with a delay of 1 second.

When the progress bar with the object name progressBarFileDownload is updated by a value of 1 and is asked to sleep for 0.5 seconds, the event loop picks up the next task; that is, it updates the progress bar with the object name progressBarVirusScan. After updating the progressBarVirusScan object name, a delay of 1 second is inserted. During this delay of 1 second, the progress bar with the object name progressBarFileDownload will be updated twice. Hence, the file download progress bar will update at double speed when compared with the progress bar with the object name progressBarVirusScan.

To work with event loops in Python, you need to install quamash on your drive. So, execute the following command:

Python -m pip install quamash

The preceding command will generate the following output:

On running the application, you will find two progress bars and a push button at the top. On clicking the Start button, both threads will start progressing. The file download progress bar will progress at double the speed of the virus scanning progress bar, as shown in the following screenshot:

Managing resources using context manager

In this recipe, you will learn to update two progress bars simultaneously using two threads. Synchronizing between the two threads and locking them will be handled through the context manager. What is context manager? Let's have a quick look.

 

 

Context manager

Context manager enables us to allocate and release resources whenever desired. To optimize the use of resources, it is essential that, when any resources are allocated by any application or thread, they are freed or cleaned up so that they can be used by some other application or thread. But sometimes the program crashes while executing, or for some other reason the program does not terminate properly, and consequently, the allocated resources are not properly freed. Context managers help in such situations by ensuring the cleaning up of allocated resources takes place. Here is a small example of using the context manager:

with method_call() as variable_name: statements that use variable_name ..........

..........

variable_name is automatically cleaned

The with keyword plays a major role in the context manager. Using the with keyword, we can call any method that returns a context manager. We assign the returned context manager to any variable by using, as variable_name. The variable_name will exist only within the indented block of the with statement, and will be automatically cleaned up when the with block ends.

Context managers are very useful while using multiple threads. While using multiple threads, you need to acquire locks when a thread accesses a common resource. Also, when a task on a common resource is performed, you need to release the lock. If the locks are not released because of some exception, it might lead to deadlocks. Context manager automatically releases the lock by making use of its with keyword. Here is a small example showing acquiring and releasing locks:

threadLock.acquire()

statements that use resource ................... ................... threadLock.release()

You can see that once the lock is acquired, resources are used and finally the lock is released. But this code might cause a disaster if the threadLock.release() command does not execute because of some exception in the preceding statements. In such a situation, it is better to use context manager. Here is the syntax for automatically releasing a lock using context manager:

with threadLock:

statements that use resource ................... ...................

lock is released automatically

You can see in the preceding syntax that the moment the with block is over, the lock is automatically released without executing the release() method.

Let's begin with creating an application in which two progress bars are updated using two threads, and the locks in the threads are handled using context manager.

How to do it…

Let’s create an application based on the Dialog without Buttons template with the following steps:

  1. We need two pair of QLabel and QProgressBar widgets in this application. Add a QLabel widget to the form by dragging and dropping a Label widget on the form.
  2. Below the Label widget, drag and drop a Progress Bar widget on the form.
  3. Repeat the procedure for another pair of Label and Progress Bar widgets.
  4. Set the text property of the first Label widget to Downloading the file and that of the second Label widget to Scanning for Virus.
  5. Set the objectName property of the first Progress Bar widget to progressBarFileDownload.
  6. Set the objectName property of the second Progress Bar widget to progressBarVirusScan.
  7. Save the application as demoTwoProgressBarsContextManager.ui. The form will now appear as shown in the following screenshot:

The user interface created with Qt Designer is stored in a .ui file, which is an XML file and needs to convert into Python code. The pyuic5 utility is used for converting the XML file into Python code. You can see the generated Python script, demoTwoProgressBarsContextManager.py, in the source code bundle for this book.

  1. Treat the demoTwoProgressBarsContextManager.py file as a header file and import it into the file from which you will invoke its user interface design.
  2. Create another Python file with the name callProgressBarContextManager.pyw and import the demoTwoProgressBarsContextManager.py code into it:
import sys
import threading
import time
from PyQt5.QtWidgets import QDialog, QApplication
from demoTwoProgressBarsContextManager import *
class MyForm(QDialog):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.show()
class myThread (threading.Thread):
    counter=0
    def __init__(self, w, ProgressBar):
        threading.Thread.__init__(self)
        self.w=w
        self.counter=0
        self.progreassBar=ProgressBar
    def run(self):
        print ("Starting " + self.name+"\n")
        with threadLock:
            while self.counter <=100:
                time.sleep(1)
                self.progreassBar.setValue(self.counter)
                self.counter+=10
                print ("Exiting " + self.name+"\n")
if __name__=="__main__":
    app = QApplication(sys.argv)
    w = MyForm()
    thread1 = myThread(w, w.ui.progressBarFileDownload)
    thread2 = myThread(w, w.ui.progressBarVirusScan)
    threadLock = threading.Lock()
    threads = []
    thread1.start()
    thread2.start()
    w.exec()
    threads.append(thread1)
    threads.append(thread2)
    for t in threads:
        t.join()
    sys.exit(app.exec_())

How it works...

You can see that there are two classes in this script: one is the main class, called the MyForm class, which does the task of interacting with the GUI form. The second class is the myThread class, which creates and invokes two threads, which in turn will update the two Progress Bar widgets used in the GUI.

The import threading statement imports Thread into the current script. Thereafter, your class, myThread, inherits the Thread class. An object of the main class, MyForm, is made, called w. Thereafter, two threads are created, thread1 and thread2, by creating two instances of the myThread class. Because thread1 is supposed to update the progress bar that represents progress in file downloading while creating it, two parameters are passed to it: the first is the instance of the main class, MyForm, and the second parameter is ProgressBar with the object name progressBarFileDownload.

The second thread, thread2, will update the progress bar that represents virus scanning, so while creating the thread2 instance two parameters are passed. The first is the MyForm class instance, w, and the second parameter is ProgressBar with the object name progressBarVirusScan. On, invoking the start method on the thread object, thread1, the run method defined in the myThread class will be invoked.

In the run method, the thread1 object does not acquire the lock but uses the context manager by calling the with threadLock block. In the with block, the resources automatically get locked. Also, the lock on resources automatically gets released when the with block completes. So, there is no need to execute the acquire method or the release method.

The progress bar with objectName progressBarFileDownload, which is being updated by thread1, will progress from 0 to 100. Once the progress bar from thread1 reaches 100, the with block completes and the release method is automatically invoked internally by thread1 (via the context manager). The thread2 object will execute its run() method once the with block of thread1 completes.

The run() method of thread2 also makes use of the context manager, so thread2 also does not have to execute the acquire() and release() methods; the context manager automatically locks the resource at the beginning of the with block, and releases the resources when the with block completes. The run() method makes the Progress Bar widget with the object name progressBarVirusScan progress from 0 to 100 with a delay of 1 second between each increment in value.

On running the application, you will find that the first progress bar that represents the file download progresses from 0% to 100% and executes completely, that is, it reaches 100%, then the second progress bar will begin progressing from 0% to 100%, as shown in the following screenshot:

'Qt5 Python GUI Programming Cookbook' 카테고리의 다른 글

Using Graphics  (0) 2023.02.28
Database Handling  (0) 2023.02.28
Networking and Managing Large Documents  (1) 2023.02.28
Understanding Dialogs  (0) 2023.02.28
Understanding OOP Concepts  (0) 2023.02.28

댓글