📜 ⬆️ ⬇️

PyQt: simple threading

Very often, programs have to use multithreading. Sometimes these are monstrous pools of threads with complex interactions, but more often this is simple code, the main requirement for which is not to freeze the interface.

PyQt has two main high-level threading tools: Python threading and Qt's QThread. For me, QThread turned out to be preferable due to better communication with the signal-slot mechanism in Qt.

So, the tool is selected, everything works fine, but over time I wanted to make working with threads somewhat easier and more convenient. There was an idea to make a bike a module for working with streams for simple cases.
')
simple_thread

This module is designed to work with threads in classes inherited from QObject. Using it, you can force any class method to run in a separate thread, while from the inside of the method you can access (albeit limited) the attributes and methods of the class.

Let's look at a simple example:
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys from time import sleep from PyQt4.QtCore import * from PyQt4.QtGui import * from simple_thread import SimpleThread class Foo(QLabel): def __init__(self, parent = None): QLabel.__init__(self, parent) self.setFixedSize(320, 240) self.digits = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'] @SimpleThread def bar(self, primaryText): rows = [] digits = self.digits for item in digits: rows.append('%s: %s' % (primaryText, item)) self.setText('\n'.join(rows), thr_method = 'b') sleep(0.5) def setText(self, text): QLabel.setText(self, text) if __name__ == "__main__": app = QApplication(sys.argv) foo = Foo() foo.show() foo.bar('From thread', thr_start = True) app.exec_() 


The class Foo is inherited from QLabel , and we want to change the text of the label once every half second, without freezing the interface. With the text output we will deal with the bar method. To make this method work in a separate thread, before declaring the method, set the decorator @SimpleThread .

From the inside of the method, we will need access to the digits attribute - a list of output words. We also need to update the label text, for this we will call the setText method.

Access to attributes

The first problem is that digits can be used not only by this thread.
The simple_thread module allows you to work around this problem. When we receive an attribute, it is not a link to this attribute that is returned to us, but a copy of it. In this case, all actions occur in the context of the main thread, and we do not need to worry about simultaneous access to an attribute from several threads.
In this way, you can refer to lists, dictionaries, and all immutable class attributes (strings, tuples, etc.).
There is one thing here - it works rather slowly, so you should not overdo it with access to attributes.

Method call

Problem number two is a call to the setText method. The problem is similar to the first one - Qt will throw an exception when trying to access a graphics class method not from the main thread. As in the first case, this is solved by suspending our thread and calling the method from the main thread.

There are three different ways to invoke methods, depending on the thr_method argument:

The thr_method argument is not passed to the method being called.

Setting Attributes

Setting attributes from another thread is also possible.
 self.newAttr = 'text' 

Attributes can be of any type.
As in the other cases, all work is carried out in the main thread and does not cause problems, it is worth remembering that by setting the attribute, you can wipe the same name as the existing one.

Start stream

To run our code, we simply execute the bar method, specifying the argument thr_start = True , so that the thread starts immediately.
There is another way, it is useful if we want to process signals called from another thread or signals of the QThread class ( started , finished , terminated ).
  thread = foo.bar('From thread') thread.finished.connect(self.barFinished) thread.start() 

Here we have connected the finished signal of the finished thread to the barFinished method and started the stream.

Stop flow

If for some reason we need to stop the running thread, we can do this by calling the thr_stop method.
  thread = foo.bar('From thread', thr_start = True) ... thread.thr_stop() 

This method sets the thr_stopFlag = True flag, the state of which we should monitor in our method and, if true, terminate the work of our method.

It is worth noting that the thr_stop method does not wait for the thread to stop, returning control immediately. If we need to wait for the thread to finish working, after thr_stop, we need to call the wait method.

The simple_thread module has two functions for stopping all active threads - terminateThreads and closeThreads . The first function rigidly interrupts the execution of all threads, which may be unsafe. The second function works as if we called thr_stop for each thread, and then wait .

All comments, suggestions and the like are welcome. If something like this has already been implemented by someone, I will be happy to link.

Source: https://habr.com/ru/post/125699/


All Articles