Форум программистов, компьютерный форум, киберфорум
iamvic
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
Путевые заметки в процессе познания Python и PyQt/PySide.
Помни - только тег CODE не портит код добавлением пробела в начало пустой строки.

К вопросу о подготовке документов LibreOffice в дополнительном потоке.

Запись от iamvic размещена 12.04.2022 в 15:24
Показов 1620 Комментарии 0

Для начала изложу некоторые свои соображения, которые народились в результате размышлений на тему "С какой стати некоторые разработчики столь рьяно отвергают одну из предлагаемых Qt схем применения QThread?"

Речь идёт о Многопоточные технологии в Qt, первый пример в табличке раздела Примеры использования (гуглоперевод интересующего отрывка причёсан мною):
Срок службы потока - разовый вызов.
Операция - запустить новую линейную функцию в другом потоке и, возможно, получать от неё информацию о ходе выполнения.
Решение - поместить вызов функции в код повторной реализации QThread.run() и стартовать QThread. В процессе работы отправлять сигналы для обновления информации о ходе выполнения.
И вроде бы всё понятно и доступно сказано. Самое главное тут, по моему мнению, - то, что функция должна быть линейной, т.е. после запуска она должна гарантированно закончиться, выполнив все свои операции за конечный отрезок времени. Управление ей не требуется.

И разве нет задач, которые надо решать именно по такой схеме? Да их полно - все задачи класса "выстрелил и забыл", например, фоновая подготовка документов к печати. Городить для выполнения таких задач схему с QThread.moveToThread() совершенно излишне на мой взгляд.

А для организации срочного завершения дополнительного потока по внезапному желанию пользователя существуют QThread.requestInterruption() и QThread.isInterruptionRequested(), начиная с PyQt5 v.5.2.

Для проверки жизнеспособности такого утверждения всего-то и требуется "скрестить ужа, ежа и трепетную лань"

Поехали...

Нажмите на изображение для увеличения
Название: splash.png
Просмотров: 401
Размер:	14.6 Кб
ID:	7443

Код модуля дополнительного потока:

thread_kit.py:
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""DemoPLOT v0.04
From iamvic on www.cyberforum.ru
Restrictions: Python 3.x, PyQt5, PostgreSQL, LibreOffice and no more.
"""
 
import copy
 
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import QMutex, QMutexLocker
from PyQt5.QtCore import QObject, QThread
from PyQt5.QtWidgets import QApplication
from PyQt5.QtSql import QSqlDatabase, QSqlQuery
 
from libre_store import print_libre_document
 
class Thread_postgresql(QThread):
    connect_parm = [
        'localhost',    # хост
        '5432',         # порт
        'postgres',     # база данных
        'postgres',     # пользователь
        'postgres',     # пароль
        '0x0'           # hex(id(связаный_индикатор))
        ]
    thread_mutex = QMutex()
    connection_created = pyqtSignal()
    duplicated_thread = pyqtSignal()
    invalid_using = pyqtSignal()
    db_or_query_invalid = pyqtSignal(str)
    no_query_results = pyqtSignal()
    lo_server_invalid = pyqtSignal()
    drop_linked_pbar = pyqtSignal(str)
    set_maximum_for_pbar = pyqtSignal(int)
    set_current_for_pbar = pyqtSignal(int)
 
    def set_connection_parm(self, parm):
        '''передать потоку параметры соединения извне
        (для наружного употребления)
        '''
        with QMutexLocker(self.thread_mutex):
            rc = (type(parm) == list and
                  len(parm) == 6 and
                  all(type(x) == str for x in parm) and
                  parm[1].isdigit() and
                  int(parm[1]) in range(1,65535))
            if rc:
                self.connect_parm = copy.deepcopy(parm)
        return rc
 
    def get_connection_parm(self):
        '''получить переданные снаружи параметры соединения
        (для внутреннего употребления)
        '''
        with QMutexLocker(self.thread_mutex):
            rc = copy.deepcopy(self.connect_parm)
        return rc
 
    def run(self):
        '''именно этот код будет выполняться в дополнительном потоке
        после вызова QThread.start()
        '''
        def inner_run(connect_name, current_parm):
            '''поскольку удалить соединение можно только вне области
            видимости соединения, то его создание и вся работа с БД
            выполняется только в теле этой функции, а удаление соединения
            производится снаружи после выхода из неё
            '''
            db = QSqlDatabase.addDatabase('QPSQL', connect_name)
            db.setHostName(current_parm[0])
            db.setPort(int(current_parm[1]))
            db.setDatabaseName(current_parm[2])
            db.setUserName(current_parm[3])
            db.setPassword(current_parm[4])
 
            if not db.open():
                self.db_or_query_invalid.emit(
                    db.lastError().text())
            else:
                query_set = QSqlQuery(db)
                rc = query_set.exec("SET application_name TO"
                                    " '{!s} (thread {!s})';".format(
                                    QApplication.applicationName(),
                                    hex(id(self))))
                if not rc:
                    self.db_or_query_invalid.emit(
                        query_set.lastError().text())
                else:
                    self.connection_created.emit()
 
                    query_data = QSqlQuery(
                        'SELECT * FROM pg_stat_activity ORDER BY pid;', db=db)
                    if query_data.size() < 1:
                        self.no_query_results.emit()
                    elif not print_libre_document(query_data, self):
                        self.lo_server_invalid.emit()
 
                db.close()
                # закрыли БД
 
            return
            # и вышли из функции
 
        proxy = QObject()
        # именовать соединение будем по идентификатору доп.потока.
        # чтобы убедиться в том, что для запуска использовался именно
        # метод QThread.start(), создаём вспомогательный объект,
        # живущий в дополнительном потоке (self живёт в основном).
        # вспомогательный объект не имеет родителя, поскольку
        # он - первый и, в данном случае, единственный на весь доп.поток.
        # а родитель, как известно, должен жить в том же потоке,
        # что и его отпрыски.
 
        if id(self) == id(proxy.thread()):
            # вспомогательный объект действительно живёт
            # в дополнительном потоке (т.е. в self)
            # => использовали именно QThread.start()
 
            connect_name = 'thread_{!s}_main_{!s}_({!s})'.format(
                # по идентификаторам дополнительного и основного потоков
                # создали уникальное имя соединения вида
                # thread_0x12345678_main_0x1234abcd_(True)
                hex(id(self)),
                # thread_0x12345678_...
                hex(id(self.thread())),
                # ...main_0x1234abcd_...
                (id(QApplication.instance().thread()) ==
                 id(self.thread()) == id(proxy.thread().thread()) )
                # ...(True)
                # (True) подтверждает, что дополнительным потоком
                # управляет именно основной (вот такая паранойя :)
                )
 
            if any(s.split('_')[1] == hex(id(proxy.thread()))
                   for s in QSqlDatabase.connectionNames()):
                # параноидальная проверка с поиском дубликата
                # идентификатора дополнительного потока
                # в именах существующих соединений
                # (совершенно лишняя, но пусть будет)
                # дубликат найден => сообщаем и выходим
                self.duplicated_thread.emit()
 
            else:
                # дубликат не найден => продолжаем работу
                current_parm = self.get_connection_parm()
                # запросили параметры соединения, переданные снаружи
 
                inner_run(connect_name, current_parm)
                # отработали с базой данных
 
                QSqlDatabase.removeDatabase(connect_name)
                # удалили соединение
 
        else:
            # вспомогательный объект живёт в потоке,
            # отличном от дополнительного (т.е. не в self)
            # => использовали QThread.run() вместо QThread.start()
            self.invalid_using.emit()
            # отправляем сигнал о неправильном использовании
 
        self.drop_linked_pbar.emit(self.get_connection_parm()[5])
        pass
        # завершили выполнение потока
        # если дополнительный поток был запущен методом QThread.start(),
        # то это вызовет отправку сигнала QThread.finished()
Остальное традиционно упаковано в прилагаемый архив.

Для работы требуется LibreOffice, Python 3, мажорная версия которого, соответствует мажорной версии Python 3 в составе LibreOffice, PyQt5 версии > 5.1 и PostgreSQL.
Вложения
Тип файла: zip demoplot4.zip (30.6 Кб, 478 просмотров)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Nekobox - outbounds[0].transport: unknown transport type: raw
damix 01.10.2026
Фикс ошибки Правым кликом по серверу -> отладочная информация -> edit Заменить "net": "raw", на "net": "tcp", Нажать кнопку reload.
Программный домашний кинотеатр
russiannick 27.09.2026
Сподобился на программный домашний кинотеатр. В качестве ЯВУ по традиции выбрал js. В помощники взял Яндекс-Алису. Было создано три зала на разные интересы. исторические и ретро сериал Хичкок. . .
Беседа с ИИ о программистах, недопускающих к созданию и правке кода генеративные ИИ и причины этого
zorxor 21.09.2026
Раньше я радовался или получал некоторые эмоции, пусть небольшие, но всё же, от самого процесса написания кода, рекомпиляции и запуска, видя постепенное развитие программы и прочее. А теперь лень. . .
Мобильное приложение ColorStep
pavlinmavlin 17.09.2026
Реализовал приложение Красный, Зеленый, Синий в Unity3d + c#. Название изменил на ColorStep. Приложение прошло модерацию и теперь доступно для скачивания. Делал его сам, шаг за шагом — и вот,. . .
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru