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

К вопросу о применении QSqlDatabase в QThread.

Запись от iamvic размещена 04.12.2021 в 14:08
Показов 1079 Комментарии 0

И всё-таки есть смысл в написании мелких "тренажёрчиков", которые в наглядном виде позволяют понять "как же оно там всё внутри устроено, что за что цепляется и как всё получается". По крайней мере, можно позволить себе некоторые вольности в формулировках, исполнении и оформлении (пункт 2 из PEP 8 - наше всё!!! ). Если требуется донести смысл происходящего (не только "что делать" и "как быть", но и "почему именно так" и чем было мотивировано то или иное решение), то в какой форме и какими словами это будет сделано - без разницы. Главное, чтобы результаты были обоснованными.

И, вот, ляпнув в одном из комментариев при обсуждении К вопросу о глобальных переменных и инкапсуляции в приложениях PyQt., что QSqlDatabase.addDatabase() вполне годен для применения именно в дополнительных потоках, озаботился я тем, что нет у меня такого рафинированного рукоделия на эту тему. Решение-то известное, простое, давно работает в разных местах, а в "тренажёрчик" не оформилось. А там есть некоторые нюансы, в том числе и эксплуатационные, которые могут крови попортить...

Долго ли, коротко ли, но результат народился. Поделка достаточно примитивная - главное окно, меню на три пункта и центральный виджет для протокола. В работе тоже проста как дверь - регистрируемся на SQL-сервере, стартуем дополнительные потоки, в протоколе наблюдаем появление и исчезновение SQL-соединений, используемых дополнительными потоками. Ограничений на количество дополнительных потоков нет (это ж моделька для испытаний, а не реальное изделие).

Соединение по умолчанию создаётся при старте приложения, после успешной регистрации выполняется всего один SQL-запрос. Стартовавшие дополнительные потоки, создав свои уникальные соединения, тоже выполняют только один SQL-запрос, а затем погружаются в сон на 10 секунд, после чего завершают свою работу.

Заточено всё под PostgreSQL.

А теперь слайды:

1. Регистрация на сервере.

Нажмите на изображение для увеличения
Название: pit_login.jpg
Просмотров: 4465
Размер:	13.8 Кб
ID:	7265

2. Что видим в протоколе после запуска дополнительных потоков.

Нажмите на изображение для увеличения
Название: pit_protocol.jpg
Просмотров: 396
Размер:	13.1 Кб
ID:	7266

Добавлено 28.08.2022:
Интересный вывод следует из этой картинки. Оказывается, что список существующих соединений, возвращаемый QSqlDatabase.connectionNames(), не упорядочен по времени создания соединения. Тут ведь первым создавалось соединение по умолчанию (qt_sql_default_connection). Наверное, можно было бы ожидать, что именно оно и будет всегда первым в списке (создаём-то все соединения с помощью QSqlDatabase.addDatabase(), что как бы намекает). Но действительность оказывается совершенно иной.

3. Что видит в это время администратор базы данных.

Нажмите на изображение для увеличения
Название: pit_pgadmin.jpg
Просмотров: 397
Размер:	56.4 Кб
ID:	7267

и демонстрация некоторых внутренностей:

1. код подкласса дополнительного потока (модуль 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
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""PostgreSQL in Thread v0.05
From iamvic on www.cyberforum.ru
Restrictions: Python 3.x, PyQt 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
 
class Thread_postgresql(QThread):
    connect_parm = [
        'localhost',    # хост
        '5432',         # порт
        'postgres',     # база данных
        'postgres',     # пользователь
        'postgres'      # пароль
        ]
    thread_mutex = QMutex()
    connection_created = pyqtSignal()
 
    def set_connection_parm(self, parm):
        '''для наружного употребления
        '''
        def check_list(x):
            rc = True
            for a in x:
                if type(a) != str:
                    rc = False
                    break
            return rc
 
        rc = False
        with QMutexLocker(self.thread_mutex):
            rc = (type(parm) == list and
                  len(parm) == 5 and
                  check_list(parm) and
                  parm[1].isdigit())
            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):
        '''именно этот код выполняется в дополнительном потоке
        '''
        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():
                # сигнал об ошибке с информацией из db.lastError().text()
                pass
            else:
                self.connection_created.emit()
                query = QSqlQuery(db)
                if query.exec(
                    "SET APPLICATION_NAME = '({!s})';".format(connect_name)):
                    # хоть какая-то идентификация для админа БД
 
                    pass
                else:
                    # сигнал об ошибке с информацией из query.lastError().text()
                    pass
 
                QThread.sleep(10)
                # тут вся остальная работа с БД, сопровождающаяся
                # периодической отправкой сигналов (если надо),
                # например, сейчас просто спим 10 сек.
                # self.sleep() не используем, потому как self живёт
                # в основном потоке (мало ли что приключится).
                # в качестве местной экзотики :) можно использовать
                # proxy.thread().sleep(), поскольку вспомогательный
                # объект живёт как раз в дополнительном потоке.
 
            db.close()
            # закрыли БД
            return
            # и вышли из функции
 
        current_parm = self.get_connection_parm()
        # запросили параметры соединения, переданные снаружи
 
        proxy = QObject()
        # вспомогательный объект живёт в дополнительном потоке,
        # а self - в основном потоке.
        # поскольку вспомогательный объект один одинёшенек на весь доп.поток,
        # то и родителя ему назначить невохможно, потому как родитель
        # тоже должен жить в дополнительном потоке,
 
        connect_name = 'thread_connection_' + hex(id(proxy.thread()))
        # по идентификатору доп.потока создали уникальное имя соединения.
 
        inner_run(connect_name, current_parm)
        # отработали с БД
 
        QSqlDatabase.removeDatabase(connect_name)
        # удалили соединение


2. код запуска дополнительного потока (вырезка из модуля main_window.py):
Кликните здесь для просмотра всего текста
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    def to_start_thread(self):
        '''стартуем дополнительный поток
        '''
        abettor = Thread_postgresql(self)
        if abettor.set_connection_parm(self.current_connection_parm):
            abettor.connection_created.connect(self.to_refresh_protocol)
            # abettor.started.connect(self.to_refresh_protocol)
            # интересная особенность - под Linux хватало сигнала started,
            # а под Windows пришлось свой сигнал городить,
            # иначе соединения не успевали попадать в протокол
 
            abettor.finished.connect(abettor.deleteLater)
            abettor.finished.connect(self.to_refresh_protocol)
            abettor.start()
        else:
            del abettor
            QMessageBox.warning(
                self, 'П Р Е Д У П Р Е Ж Д Е Н И Е !!!',
                'Нарушение структуры параметров, передаваемых'
                ' в дополнительный поток.\n'
                'Дополнительный поток не создан.')


Весь исходный код можно найти в прилагаемом архивчике. Ничего сложного, невозможного - нет. Главное, не лениться... И если там чего не так, то стесняться не надо. С удовольствием приму любые замечания, высказанные в любой форме, вплоть до инвективной лексики (если их пропустят модераторы), с одним условием - они должны иметь обоснование. Если "плохо", то далее следует "почему плохо" с подробным описанием Вашего понимания ситуации. Если даёте ссылочку, то расскажите как Вы понимаете то, что там написано, чтоб я мог сравнить со своим пониманием прочитанного по этой ссылке. Вполне возможно, что я ошибаюсь и ничего в этом зазорного нет - человеку свойственно ошибаться, все мы постоянно учимся на ошибках.

PS: однако, реализуя имеющуюся возможность в реальных проектах, не стоит забывать о возможных последствиях. Иначе, небывалая живость администратора базы данных может обернуться неприятным сюрпризом. SQL-шторм никого не оставляет равнодушным...

Доработанная версия приложения выложена в записи блога К вопросу о переключениях GUI с одного потока на другой в приложениях PyQt.
Вложения
Тип файла: zip pg_th_probe.zip (8.1 Кб, 474 просмотров)
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Скрипты 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) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru