Search code examples
pythonpython-3.xpython-multithreadingqthreadpyside2

Program has to wait till two threads (Sql query execution) are completed


I have a program which compares to DB table values and i have created a GUI in PyQt5. I have created two threads one for querying each table and then program has to wait till two threads are completed. My code below

from PySide2 import QtWidgets
from PySide2 import QtGui
from PySide2 import QtCore
from Main_interface import Ui_mainWindow
import pandas as pd


class mainWindow(QtWidgets.QMainWindow, Ui_mainWindow):

  sqlClicked1 = QtCore.Signal(str)
  sqlClicked2 = QtCore.Signal(str)

  def __init__(self, parent=None):
    super(mainWindow, self).__init__(parent)
    self.setupUi(self)

    self.thread = QtCore.QThread(self)
    self.thread.start()
    self.obj = Worker()
    self.obj.moveToThread(self.thread)
    self.sqlClicked.connect(self.obj.runsql_MC)
    self.sqlClicked1.connect(self.obj.runsql_IRI)
    self.obj.error.connect(self.on_error)


 def run_report(self):
    sqlquery1 = "Select * from table1"
    sqlquery2 = "Select * from table2"

    df1 = self.sqlClicked1.emit(sqlquery1)
    df2 = self.sqlClicked2.emit(sqlquery2)

    self.sqlClicked1.finished.connect(self.on_finished)
    self.sqlClicked2.finished.connect(self.on_finished)

    print("SQL execution is done")

    #Then i am calling function to compare two dataframes


class Worker(QtCore.QObject):
   finished = QtCore.Signal()
   result = QtCore.Signal(object)

   @QtCore.Slot(str)
   def runsql_MC(self, sqlquery_MC):
     print("Thread1 is working")
     try:
        df1 = pd.read_sql(sql=sqlquery_MC, con=cnxn)
     except:
        traceback.print_exc()
     else:
        self.signals.result.emit(df1)  # Return the result of the processing
     finally:
        self.signals.finished.emit()  # Done

  @QtCore.Slot(str)
  def runsql_IRI(self, sqlquery_IRI):
    print("Thread2 is working")
    try:
        df2 = pd.read_sql(sql=sqlquery_IRI, con=cnxn)
    except:
        traceback.print_exc()
    else:
        self.signals.result.emit(df2)  
    finally:
        self.signals.finished.emit()  


if __name__ == '__main__':
  import sys
  app = QtWidgets.QApplication(sys.argv)
  my_app = mainWindow()
  my_app.show()
  sys.exit(app.exec_())

self.sqlClicked1.emit(sqlquery1) and self.sqlClicked2.emit(sqlquery2) is calling corresponding threads runsql_MC() and runsql_IRI. Then I need to wait till two threads are completed to start comparison process. Currently its not happening.


Solution

  • Although your code is not an MRE, show your ignorance of various concepts.

    • The emission of a signal does not imply obtaining the data as a result since it will be sent asynchronously.

    • In your code even if you invoke 2 queries does not imply that each one runs on different threads since the worker lives in a single thread.

    • Your runsql_MC and runsql_IRI methods are redundant since they are a template of the same thing.

    • In addition to other errors such as that there is no object/signal called sqlClicked, you have not declared the object signals, etc.

    The idea is to have a worker who lives in a different thread for each query, and create a class that handles the workers waiting for the data and eliminating when they have finished their work.

    from functools import partial
    import sqlite3
    import pandas as pd
    from PySide2 import QtCore, QtGui, QtWidgets
    
    
    class Worker(QtCore.QObject):
        finished = QtCore.Signal()
        result = QtCore.Signal(object)
    
        @QtCore.Slot(str)
        def runsql(self, query):
            cnxn = sqlite3.connect("test.db")
            print("Thread1 is working")
            try:
                df1 = pd.read_sql(sql=query, con=cnxn)
            except:
                traceback.print_exc()
            else:
                self.result.emit(df1)  # Return the result of the processing
            finally:
                self.finished.emit()  # Done
    
    
    class SqlManager(QtCore.QObject):
        results = QtCore.Signal(list)
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self.workers_and_threads = {}
            self.dataframes = []
    
        def execute_queries(self, queries):
            for query in queries:
                thread = QtCore.QThread(self)
                thread.start()
                worker = Worker()
                worker.result.connect(self.onResults)
                worker.moveToThread(thread)
                self.workers_and_threads[worker] = thread
                # launch task
                wrapper = partial(worker.runsql, query)
                QtCore.QTimer.singleShot(0, wrapper)
    
        @QtCore.Slot(object)
        def onResults(self, result):
            worker = self.sender()
            thread = self.workers_and_threads[worker]
            thread.quit()
            thread.wait()
            del self.workers_and_threads[worker]
            worker.deleteLater()
            self.dataframes.append(result)
            if not self.workers_and_threads:
                self.results.emit(self.dataframes)
                self.dataframes = []
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.push_button = QtWidgets.QPushButton("Run Report")
            self.push_button.clicked.connect(self.run_report)
            self.setCentralWidget(self.push_button)
    
            self.manager = SqlManager(self)
            self.manager.results.connect(self.onResults)
    
        @QtCore.Slot()
        def run_report(self):
            sqlquery1 = "Select * from table1"
            sqlquery2 = "Select * from table2"
            queries = [sqlquery1, sqlquery2]
            self.manager.execute_queries(queries)
            self.push_button.setEnabled(False)
    
        @QtCore.Slot(list)
        def onResults(self, dataframes):
            print(dataframes)
            self.push_button.setEnabled(True)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        my_app = MainWindow()
        my_app.show()
        sys.exit(app.exec_())