Preocupado com as condições de corrida ao acessar a conexão com o banco de dados SQLite3 acessada no encadeamento invocado pelo ouvinte Pynput dentro de um QThread

Estou escrevendo um aplicativo do Windows com o Pyside2. Devido à natureza de como estou usando o multithreading, estou tendo que interagir com o mesmo banco de dados Sqlite3 em vários threads. Eu criei uma linha <100Exemplo mínimo, completo e verificável que quase idêntica replica o problema.

O problema: Atualmente, estou usando omódulo pynput monitorar a atividade das teclas em segundo plano depois que o PushButton for pressionado, enquanto a GUI Qt estiver fora de foco para uma combinação de teclas de atalho de "j" + "k". Depois que a combinação de teclas de atalho é pressionada, uma captura de tela é feita, a imagem é processada via OCR e salva em um banco de dados junto com o texto do OCR. O caminho da imagem é enviado através de uma série de sinais conectados ao thread da GUI principal. O monitoramento chave acontece em outroQThread para impedir que o monitoramento de chaves e o processamento de imagens afetem o loop de eventos Qt principal. Depois que o QThread inicia e emite seu sinal de início, chamo omonitor_for_hot_key_combo função na instância key_monitor que instancialistenercomo umthreading.Thread, às quais são atribuídas funções de membro key_monitoron_release eon_press como retornos de chamada, que são chamados toda vez que uma tecla é pressionada.

É aqui que está o problema. Esses retornos de chamada interagem com oimageprocessing_obj instância doimage_processclasse em um segmento diferente do qual a classe foi instanciada. Portanto, quandoimage_process Como as funções membro são interagidas com o uso do banco de dados SQlite, elas o fazem em um encadeamento separado em que a conexão com o banco de dados foi criada.Agora, o SQLite "pode ser usado com segurança por vários threads providenciou quenenhuma conexão única com o banco de dados é usada simultaneamente em dois ou mais threads ". Para permitir isso, você deve definir ocheck_same_thread argumento parasqlite3.connect() para Falso. No entanto, eu preferiria evitar esse acesso multithread do banco de dados, se possível, para evitar um comportamento indefinido.

A solução possível: Fiquei me perguntando se os dois threads, ambosthreading.Thread eQThread não são necessários e tudo pode ser feito no segmento Pynput. No entanto, parece que não consigo descobrir como usar o thread Pynput enquanto ainda é capaz de enviar sinais de volta ao loop de eventos Qt principal.

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)

questionAnswers(1)

yourAnswerToTheQuestion