reocupado por las condiciones de carrera al acceder a la conexión de base de datos SQLite3 a la que se accede en el hilo invocado por Pynput listener dentro de un QThread

Estoy escribiendo una aplicación de Windows con Pyside2. Debido a la naturaleza de cómo estoy usando subprocesos múltiples, tengo que interactuar con la misma base de datos Sqlite3 en múltiples subprocesos. He creado una línea <1 Ejemplo mínimo, completo y verificable que casi idénticamente replica el problema.

El problema Actualmente estoy usando lapynput module para monitorear la actividad clave en segundo plano una vez que se ha presionado el PushButton, mientras que la GUI Qt está fuera de foco para una combinación de teclas de acceso rápido de "j" + "k". Una vez que se presiona la combinación de teclas de acceso rápido, se toma una captura de pantalla, la imagen se procesa a través de OCR y se guarda en una base de datos junto con el texto de OCR. La ruta de la imagen se envía a través de una serie de señales conectadas al hilo principal de la GUI. El monitoreo clave ocurre en otraQThread para evitar que el monitoreo clave y el procesamiento de imágenes afecten la ejecución del bucle principal de eventos Qt. Una vez que el QThread se inicia y emite su señal de inicio, llamo amonitor_for_hot_key_combounción @ en la instancia de key_monitor que instancialistenercomo unthreading.Thread, que tiene asignadas funciones miembro key_monitoron_release yon_press como devoluciones de llamada, que se llaman cada vez que se presiona una tecla.

Aquí es donde radica el problema. Esas devoluciones de llamada interactúan con laimageprocessing_obj instancia de laimage_processclass en un hilo diferente al que se instancia la clase. Por lo tanto, cuandoimage_process las funciones miembro interactúan con las que usan la base de datos SQlite, lo hacen en un subproceso diferente al que se creó la conexión de la base de datos. Ahora, SQLite "puede ser usado de manera segura por múltiples hilos siempre queno se utiliza una conexión de base de datos única simultáneamen en dos o más hilos ". Para permitir esto, debe configurar elcheck_same_thread argumento parasqlite3.connect() a falso. Sin embargo, preferiría evitar este acceso multiproceso de la base de datos si es posible para evitar un comportamiento indefinido.

La solución posible: Me he estado preguntando si los dos hilos, ambosthreading.Thread yQThread no son necesarios y todo se puede hacer dentro del hilo de Pynput. Sin embargo, parece que no puedo entender cómo usar el hilo de Pynput sin dejar de enviar señales al bucle principal de eventos 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)

Respuestas a la pregunta(1)

Su respuesta a la pregunta