Я пишу приложение Windows с Pyside2. Из-за того, как я использую многопоточность, мне приходится взаимодействовать с одной и той же базой данных Sqlite3 в нескольких потоках. Я создал ‹100-строчный минимальный, полный, проверяемый пример, который почти идентично воспроизводит проблему.
Проблема: в настоящее время я использую модуль pynput для мониторинга ключа активность в фоновом режиме после нажатия кнопки PushButton, в то время как графический интерфейс Qt не в фокусе для комбинации горячих клавиш «j» + «k». После нажатия комбинации горячих клавиш делается снимок экрана, изображение обрабатывается с помощью OCR и сохраняется в базе данных вместе с текстом OCR. Путь изображения отправляется через ряд подключенных сигналов в основной поток графического интерфейса. Мониторинг ключей происходит в другом QThread
, чтобы предотвратить влияние мониторинга ключей и обработки изображений на выполнение основного цикла событий Qt. Как только QThread запускается и выдает сигнал запуска, я вызываю функцию monitor_for_hot_key_combo
в экземпляре key_monitor, который создает экземпляр listener
как threading.Thread
, которому назначаются функции-члены key_monitor on_release
и on_press
в качестве обратных вызовов, которые вызываются каждый раз при нажатии клавиши.
Вот в чем проблема. Эти обратные вызовы взаимодействуют с экземпляром imageprocessing_obj
класса image_process
в потоке, отличном от того, в котором был создан экземпляр класса. Поэтому, когда взаимодействуют функции-члены image_process
, использующие базу данных SQlite, они делают это в отдельном потоке, чем соединение с базой данных, созданное в . Теперь SQLite "может безопасно использоваться несколькими потоками при условии, что ни одна база данных соединение используется одновременно в двух или более потоках». Чтобы разрешить это, вы должны установить аргумент check_same_thread
для sqlite3.connect()
в False. Тем не менее, я предпочитаю избегать многопоточного доступа к базе данных, если это возможно, чтобы предотвратить неопределенное поведение.
Возможное решение: мне интересно, не нужны ли два потока, threading.Thread
и QThread
, и все это можно сделать в потоке Pynput. Однако я не могу понять, как просто использовать поток Pynput, сохраняя при этом возможность отправлять сигналы обратно в основной цикл событий Qt.
qtui.py
from PySide2 import QtCore, QtWidgets
from PySide2.QtCore import *
import HotKeyMonitor
class Ui_Form(object):
def __init__(self):
self.worker = None
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.pressbutton = QtWidgets.QPushButton(Form)
self.pressbutton.setObjectName("PushButton")
self.pressbutton.clicked.connect(self.RunKeyMonitor)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))
self.pressbutton.setText(QtWidgets.QApplication.translate("Form", "Press me", None, -1))
def RunKeyMonitor(self):
self.Thread_obj = QThread()
self.HotKeyMonitor_Obj = HotKeyMonitor.key_monitor()
self.HotKeyMonitor_Obj.moveToThread(self.Thread_obj)
self.HotKeyMonitor_Obj.image_processed_km.connect(self.print_OCR_result)
self.Thread_obj.started.connect(self.HotKeyMonitor_Obj.monitor_for_hotkey_combo)
self.Thread_obj.start()
def print_OCR_result(self, x):
print("Slot being called to print image path string")
print(x)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
HotKeyMonitor.py
from pynput import keyboard
from PySide2.QtCore import QObject, Signal
import imageprocess
class key_monitor(QObject):
image_processed_km = Signal(str)
def __init__(self):
super().__init__()
self.prev_key = None
self.listener = None
self.imageprocessing_obj = imageprocess.image_process()
self.imageprocessing_obj.image_processed.connect(self.image_processed_km.emit)
def on_press(self,key):
pass
def on_release(self,key):
if type(key) == keyboard._win32.KeyCode:
if key.char.lower() == "j":
self.prev_key = key.char.lower()
elif key.char.lower() == "k" and self.prev_key == "j":
print("key combination j+k pressed")
self.prev_key = None
self.imageprocessing_obj.process_image()
else:
self.prev_key = None
def stop_monitoring(self):
self.listener.stop()
def monitor_for_hotkey_combo(self):
with keyboard.Listener(on_press=self.on_press, on_release = self.on_release) as self.listener:self.listener.join()
imageprocess.py
import uuid,os,sqlite3,pytesseract
from PIL import ImageGrab
from PySide2.QtCore import QObject, Signal
class image_process(QObject):
image_processed = Signal(str)
def __init__(self):
super().__init__()
self.screenshot = None
self.db_connection = sqlite3.connect("testdababase.db", check_same_thread=False)
self.cursor = self.db_connection.cursor()
self.cursor.execute("CREATE TABLE IF NOT EXISTS testdb (OCRstring text, filepath text)")
def process_image(self):
self.screenshot = ImageGrab.grab()
self.screenshot_path = os.getcwd() + "\\" + uuid.uuid4().hex + ".jpg"
self.screenshot.save(self.screenshot_path )
self.ocr_string = pytesseract.image_to_string(self.screenshot)
self.cursor.execute("INSERT INTO testdb (OCRstring, filepath) VALUES (?,?)",(self.ocr_string, self.screenshot_path))
self.image_processed.emit(self.screenshot_path)
threading.Thread
, как указано в документации. Поэтому у меня есть Qthread, который вы видите в основном модуле qtgui.py, иthreading.thread
, который вы видите в модуле hotkeymonitor.py. - person Mrcitrusboots   schedule 10.07.2018threading.Thread
, я ищу способ автоматически использовать Qthread, который я уже создал для слушателя, чтобы избежать многопоточного доступа к базе данных SQlite. - person Mrcitrusboots   schedule 10.07.2018