SQLAlchemy: Arbeiten mit mehreren QThreads?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 16:46

@__deets__: Ich habe meine grobe Skizze etwas umstrukturiert, weil ich eine bessere Teilung vornehmen möchte. Der Quelltext ist extrem gekürzt und nicht lauffähig! Wenn ich also __deets__s, snafus und BlackJacks Ratschläge streng verfolge, dann sieht meine Umstrukturierung und mein Gedankenproblem wie folgt aus.

Zunächst ein paar Anmerkungen zu meiner Umstrukturierung. Ich habe eine Teilung zwischen Session und Engine vorgenommen. Es gibt die ManagedEgine()-Klasse, die nur einmalig aufgerufen wird. In dieser Klasse wird in einem Atemzug die create_engine() und auch der sessionmaker() erstellt. Es sind beides Objekte, die man nur einmal erstellt. Dann haben wir die ManagedSessionScope()-Klasse, die bei Bedarf immer wieder aufgerufen werden kann.

Dann haben wir die MyCustomDialog()-Klasse, die für die View verantwortlich ist. In dieser Klasse sehen wir einmal die log_in_database()-Methode. Klickt der Benutzer auf die Schaltfläche "LogIn" wird er/sie angemeldet. Es wird eine Instanzt der ManagedEgine()-Klasse erstellt und sessionmaker() wird klassenweit in das self.managed_engine-Attribut gespeichert. Nach dem erfolgreichen LogIn klickt der Benutzer auf die Schaltfläche "Daten abfragen" und ruft dadurch die start_all_selection()-Methode auf. Und genau dort habe ich das Problem. Aber dazu komme ich noch.

Darüber hinaus habe ich eine Worker()-Klasse, die durch die aufgerufenen Threads verwaltet werden. In dieser Worker-Klasse sehen wir, dass dort mit der MasterDataManipulation()-Klasse gewerkelt wird.

Un zu guter Letzt haben wir eine MasterDataManipulation() -Klasse, in welcher die ganzen Abfragen und weitere Operatoren (Update, Add, Delete) befinden. Diese Klasse wird bei jedem Thread erneut instanziiert.

Gedankenproblem:
Ich habe bisher die Session deshalb stets an die Threads übergeben, weil die Sessions in den MasterDataManipulation()-Klassen gebraucht werden, in welcher die Operationen stattfinden. Ursprünglich war es so gedacht: Bei jedem Thread-Start wird jeweils eine Instanz von der MasterDataManipulation()-Klasse erstellt, die Sessions werden dorthin übergeben und anschließend werden die Abfragen bearbeitet. Wenn ich das wie __deets__ und snafu mache, dann müsste ich die ganzen Abfragen in meinen Hauptthread (in dem Falle wäre es die GUI-Klasse) verlegen. Jedoch wollte ich es vermeiden, weil ich nicht will, dass die GUI einfriert, wenn die ganzen Abfragen im Hautthread ausgeführt werden. Und genau da liegt mein Gedankenproblem.

  1. [...] # All imports are here
  2.  
  3. [...]
  4.  
  5. class ManagedEgine(object):
  6.     def __init__(self,
  7.                  dbms=None,
  8.                  dbdriver=None,
  9.                  dbuser=None,
  10.                  dbuser_pwd=None,
  11.                  db_server_host=None,
  12.                  dbport=None,
  13.                  db_name=None,
  14.                  echo_verbose=True):
  15.        
  16.         self.dbms = dbms
  17.         self.dbdriver = dbdriver
  18.         self.dbuser = dbuser
  19.         self.dbuser_pwd = dbuser_pwd
  20.         self.db_server_host = db_server_host
  21.         self.dbport = dbport
  22.         self.db_name = db_name
  23.         self.echo_verbose = echo_verbose
  24.         url = '{}+{}://{}:{}@{}:{}/{}'.format(
  25.             self.dbms, self.dbdriver, self.dbuser, self.dbuser_pwd, self.db_server_host, self.dbport, self.db_name)
  26.  
  27.         _engine = create_engine(url, echo=self.echo_verbose)
  28.        
  29.         # I have to persist all tables and create them
  30.         Base.metadata.create_all(_engine)
  31.  
  32.         # Create the session factory
  33.         self.session_factory = sessionmaker(bind=_engine)
  34.        
  35. class ManagedSessionScope(object):
  36.     def __init__(self, engine=None):
  37.  
  38.         self._enginge = engine
  39.  
  40.         self.session = None
  41.  
  42.         self._Session = scoped_session(self._enginge)
  43.  
  44.     def __enter__(self):
  45.         self.session = self._Session # this is now a scoped session
  46.                                      # sqlalchemy.orm.scoping.scoped_session
  47.         return self.session
  48.  
  49.     def __exit__(self, exception, exc_value, traceback):
  50.         try:
  51.             if exception:
  52.                 self.session.rollback()
  53.             else:
  54.                 self.session.commit()
  55.         finally:
  56.             self.session.close()
  57.  
  58. class MasterDataManipulation(object):
  59.  
  60.     def __init__(self):
  61.  
  62.     def select_all(self, category):
  63.  
  64.         dict_store_session_query = {
  65.                                     'person_gender': lambda: self._session.query(PERSON_GENDER),
  66.                                      [...]
  67.                                      }
  68.  
  69.         try:
  70.  
  71.             for record in dict_store_session_query[category]():
  72.                 if category == 'person_gender':
  73.                     yield record.id, record.gender
  74.             self._session.commit()
  75.  
  76.         except Exception:
  77.             self._session.rollback()
  78.            
  79. class Worker(QObject):
  80.  
  81.     [...] # Signals are here  
  82.     def __init__(self,
  83.                  combo_box=None,
  84.                  category=None,
  85.                  time_interval=None,
  86.                  parent=None):
  87.         QObject.__init__(self, parent)
  88.  
  89.         [...]
  90.  
  91.     def init_object(self):
  92.         self.timer = QTimer()
  93.  
  94.         master_data_manipulation = MasterDataManipulation(session_object=self.new_scope)
  95.         query_data=master_data_manipulation.select_all
  96.         self._element = query_data(self.category)
  97.  
  98.         self.timer.setSingleShot(False)
  99.         self.timer.setInterval(int(self.time_interval))
  100.         self.timer.timeout.connect(self.populate_item)
  101.         self.timer.start()
  102.    
  103.     [...]
  104.            
  105. class MyCustomDialog(QDialog):
  106.  
  107.     [...] Here are some signals
  108.  
  109.     def __init__(self, parent=None):
  110.         QDialog.__init__(self, parent)
  111.         [...]
  112.         self.managed_engine = None
  113.         [...]
  114.     def log_in_database(self):
  115.         # User wants to program to log in
  116.         dbms ="mysql"
  117.         dbdriver="pymysql"
  118.         dbuser="root"
  119.         dbuser_pwd="xxx"
  120.         db_server_host="localhost"
  121.         dbport=3306
  122.         db_name="test"
  123.         echo_verbose=True
  124.  
  125.         try:
  126.  
  127.             managed_engine = ManagedEgine(dbms=dbms,
  128.                                                dbdriver=dbdriver,
  129.                                                dbuser=dbuser,
  130.                                                dbuser_pwd=dbuser_pwd,
  131.                                                db_server_host=db_server_host,
  132.                                                dbport=dbport,
  133.                                                db_name=db_name,
  134.                                                echo_verbose=echo_verbose)
  135.            
  136.             self.managed_engine = managed_engine.session_factory
  137.            
  138.         except SQLAlchemyError:
  139.             desired_trace = format_exc(exc_info())
  140.             print "desired_trace", desired_trace
  141.  
  142.     def start_thread(self,
  143.                      combo_box=None,
  144.                      session=None,
  145.                      time_interval=None,
  146.                      category=None):
  147.  
  148.         task_thread = QThread(self)
  149.  
  150.         task_thread.work = Worker(new_scope=session,
  151.                                   time_interval=time_interval,
  152.                                   category=category,
  153.                                   combo_box=combo_box)
  154.  
  155.         task_thread.work.moveToThread(task_thread)
  156.         [...]
  157.         task_thread.started.connect(task_thread.work.init_object)
  158.         [...]
  159.        
  160.     [...]
  161.     def start_all_selection(self):
  162.         [...]
  163.         list_tuple = [
  164.                     ("person_salutation", self.combo_person_salutation),
  165.                     ("person_title", self.combo_person_title),
  166.                     ("person_gender", self.combo_person_gender),
  167.                     ("person_religion", self.combo_person_religion),
  168.                     ("person_eye_color", self.combo_person_eye_color),
  169.                     ("person_hair_color", self.combo_person_hair_color),
  170.                     ("person_relationship_status", self.combo_person_relationship_status),
  171.                     ("person_nationality", self.combo_person_nationality)
  172.                        ]
  173.  
  174.         try:
  175.             #with ManagedSessionScope(engine=self.managed_engine) as session_that_scoped:
  176.             # Here we don't want to share one session with other threads
  177.             # What should I do?
  178.                
  179.             for category, combobox in list_tuple:
  180.  
  181.                 combobox.clear()
  182.  
  183.                 self.start_thread(combo_box=combobox,
  184.                                   session=session_that_scoped,
  185.                                   time_interval=100,
  186.                                   category=category)
  187.  
  188.         except SQLAlchemyError as err:
  189.             [...] # Do stuff with Exception
  190.         except OperationalError as OpErr:
  191.             [...] # Do stuff with Exception
Zuletzt geändert von Sophus am Montag 14. August 2017, 17:04, insgesamt 4-mal geändert.
Benutzeravatar
__deets__
User
Beiträge: 2140
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon __deets__ » Montag 14. August 2017, 16:59

Sophus hat geschrieben: Wenn ich das wie __deets_ und snafu mache, dann müsste ich die ganzen Abfragen in meinen Hauptthread (in dem Falle wäre es die GUI-Klasse) verlegen. Jedoch wollte ich es vermeiden, weil ich nicht will, dass die GUI einfriert, wenn die ganzen Abfragen im Hautthread ausgeführt werden. Und genau da liegt mein Gedankenproblem.


Wie *kommst* du denn auf sowas? Ich habe dir doch Code gepostet, der belegt, dass ich Abfragen ueber die Session in verschiedenen Threads mache. JEDER hier hat verstanden, was du willst.

Du benutzt die scoped session einfach komplett falsch. Du brauchst davon genau *eine*, und die kannst du auch ausnahmsweise auf Modul-Ebene anlegen (so macht SQLAlchemy das selbst, und erklaert ausfuehrlich, warum das ausnahmsweise nicht boese(tm) ist)

Ich habe jetzt hier schon mehrfach code gepostet, der das illustriert. Schmeiss mal deinen ganzen Managed*-Wahnsinn weg, und erstell dir ein ganz einfaches Qt-Programm welches

- *eine* scoped session auf Modul-Ebene erzeugt.
- einen Dialog hat, der auf Knopfdruck eine Query absetzt und die Ergebnisse einfach ausdruckt mit print
- einen Hintergrund-Thread started, welcher periodisch dasselbe macht, und einfach die Ergebnisse ausdruckt mit print.

Und streu ein "print(id(Session())" ein, um dich zu ueberzeugen, dass der Thread eine andere Session benutzt.

Natuerlich kann man statt einer globalen Variablen auch das scoped_session-Objekt in die Threads (und jede moegliche andere Stelle, die SQL taetigt) reinreichen. Aber das ist nicht die Art, wie man das in SQLAlchemy ueblicherweise macht. Und *wenn* man es macht, dann reicht man die eine scoped_session rein, und legt nicht ueberall eine neue an. So wie du in Zeile 43.
Benutzeravatar
__deets__
User
Beiträge: 2140
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon __deets__ » Montag 14. August 2017, 17:16

Hier mal ein multi-threaded und self-contained Beispiel das belegt, dass der Ansatz funktioniert und auch wunderbar parallel Daten abholt:

  1. import time
  2. import random
  3. import threading
  4.  
  5. from sqlalchemy import create_engine
  6. from sqlalchemy.orm import scoped_session
  7. from sqlalchemy.orm import sessionmaker
  8.  
  9.  
  10. session_factory = sessionmaker()
  11. Session = scoped_session(session_factory)
  12.  
  13.  
  14. def work():
  15.     print(id(Session()))
  16.     for _ in xrange(5):
  17.         time.sleep(random.random() * 3)
  18.         res = Session.execute("SELECT * FROM test LIMIT 3")
  19.         for row in res:
  20.             print(threading.currentThread().getName(), row)
  21.             time.sleep(.1)
  22.  
  23.  
  24. def main():
  25.     eng = create_engine('sqlite:////tmp/test.db')
  26.     Session.configure(bind=eng)
  27.  
  28.     Session.execute('CREATE TABLE IF NOT EXISTS test ( data int)')
  29.     for i in xrange(1000):
  30.         Session.execute('INSERT INTO test (data) values (:param)',  { "param": i})
  31.  
  32.     Session.commit()
  33.  
  34.     print(id(Session()))
  35.     threads = [threading.Thread(target=work) for _ in xrange(10)]
  36.     [t.start() for t in threads]
  37.     [t.join() for t in threads]
  38.  
  39. if __name__ == '__main__':
  40.     main()
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 17:22

@__deets__: Ich will nicht auf Modul-Ebene arbeiten, denn jede Klasse, die ich hier extrem verkürzt dargestellt habe, präsentiert bei mir jedes Modul. Für jede Klasse ein Modul. Demzufolge wüsste ich nicht woich die scoped_session(), zusammen mit create_engine() und sessionmaker() auf Modulebene anlegen soll? In einem Modul (GUI) findet die Anmeldung zur Datenbank statt, in ein anderes Modul (ebenfalls GUI) sind dann diese Schaltflächen, dass ManagedEngine() und ManagedSessionScope() sind je in einem Modul untergebracht. Und auch die MasterDataManipulation() ist in einem Modul. Deswegen habe ich auch die strikte Teilung zwischen Engine und Session vorgenommen, damit ich die Engine klassenweit benutze, und wenn ich in eine andere GUI gehe, reiche ich die bereits erstellte Engine einfach weiter. Ich komme mir gerade mächtig dumm for, als jemand, dem man sagt "1+1=2" und mich dann fragt "was sind 2+2=?" und ich dann sage "na 2".

Und das threading-Modul bringt mir leider nichts, weil mir das mit den Klassen vollkommen auf der Strecke bleibt. Ich versuche mal das Beispiel zu verstehen, und dann auf meinen Klassen anzuwenden.
Benutzeravatar
__deets__
User
Beiträge: 2140
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon __deets__ » Montag 14. August 2017, 17:46

Es ist ueblich fuer SQLAlchemy die Session in einem Modul zu definieren und dann in anderen Modulen eben einfach zu importieren. Und in main() bindet man die konkrete Engine (die ja zB von Benutzereinstellungen abhaengen kann etc) an die Session. Wie vorgemacht.

Und das threading-Modul unterscheided sich bezueglich des gesagten nicht von QThreads, insofern verstehe ich nicht, warum dir "das nichts bringt". Das man fuer Qt QThreads braucht ist schoen, SQLAlchemy ist das egal.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 17:54

__deets__ hat geschrieben:Und das threading-Modul unterscheided sich bezueglich des gesagten nicht von QThreads, insofern verstehe ich nicht, warum dir "das nichts bringt". Das man fuer Qt QThreads braucht ist schoen, SQLAlchemy ist das egal.


Na ja, meine QThread-Struktur ist doch ganz anders, da unterscheidet sich eine ganze Menge. In meinem Fall wird die Worker()-Klasse zum QThread() hinzugefügt. Bei dir spielt sich alles auf einfachen Funktionen ab. Da ist nichts, einfach Funktionen an den Threads zu übergeben. Deine Struktur ist weitaus unterschiedlicher als meine Struktur, und daher komme ich gerade gar nicht mit.
Benutzeravatar
__deets__
User
Beiträge: 2140
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon __deets__ » Montag 14. August 2017, 18:01

Da ich ueberhaupt *NICHTS* an meine Thread-targets uebergebe, ist das eigentlich voellig gleichgueltig, wie diese Funktionen zur Ausfuehrung gelangen. Die kannst du auch in work() aufrufen, bzw. den Code ersetzen.

Mir scheint es eher so, als ob du schon viel investiert hast in deinen Code, und jetzt Aenderungen lieber einarbeiten wuerdest, statt dir mal von Grund auf klar zu machen, wie man mit threading und SQLAlchemy arbeitet. Halte ich fuer einen verfehlten Ansatz. Bau lieber ein kleines Beispiel das geht, und lerne daran, statt dein Monstrum zaehmen zu wollen.
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 19:24

Gut, dann basteln wir mal was. Wobei ich anmerken muss, dass mein Quelltext nicht funktioniert. Irgendwo scheint etwas zu klemmen. Aber das ist erst einmal nebensächlich, weil es mir darum geht, zu erkennen, ob ich deine ausführlichen Aussagen verstanden habe. Damit meine Spielerei auch meinem Projekt etwas nahe kommt, habe ich insgesamt fünf Module eingerichtet.

defined_session.py
Du hast gesagt, dass sowohl session_factory () als auch scoped_session() auf Modulebene angelegt werden können, und diese dann in anderen Modulen nur zu importieren brauche.
  1. from sqlalchemy.orm import scoped_session
  2. from sqlalchemy.orm import sessionmaker
  3.  
  4. session_factory = sessionmaker()
  5. Session = scoped_session(session_factory)


managed_engine.py
Für meine Engine habe ich mir die Freiheit erlaubt, und die separate Klasse erstellt.
  1. from sqlalchemy import create_engine
  2. from sqlalchemy.orm import scoped_session
  3. from sqlalchemy.orm import sessionmaker
  4. from sqlalchemy.ext.declarative import declarative_base
  5.  
  6. ''' setting up root class for declarative declaration '''
  7. Base = declarative_base()
  8.  
  9. class ManagedEngine(object):
  10.     def __init__(self,
  11.                  dbms=None,
  12.                  dbdriver=None,
  13.                  dbuser=None,
  14.                  dbuser_pwd=None,
  15.                  db_server_host=None,
  16.                  dbport=None,
  17.                  db_name=None,
  18.                  echo_verbose=True):
  19.        
  20.         self.dbms = dbms
  21.         self.dbdriver = dbdriver
  22.         self.dbuser = dbuser
  23.         self.dbuser_pwd = dbuser_pwd
  24.         self.db_server_host = db_server_host
  25.         self.dbport = dbport
  26.         self.db_name = db_name
  27.         self.echo_verbose = echo_verbose
  28.         url = '{}+{}://{}:{}@{}:{}/{}'.format(
  29.             self.dbms, self.dbdriver, self.dbuser, self.dbuser_pwd, self.db_server_host, self.dbport, self.db_name)
  30.  
  31.         self._engine = create_engine(url, echo=self.echo_verbose)
  32.        
  33.         # I have to persist all tables and create them
  34.         Base.metadata.create_all(self._engine)
  35.  


managed_data_manipulation.py
In dieser Klasse liegen meine Operatoren, und ich habe sessionmaker() und scoped_session() importiert.
  1. from defined_session import*
  2.  
  3. class MasterDataManipulation(object):
  4.  
  5.     def __init__(self):
  6.  
  7.         self.attr = None
  8.  
  9.     def select_all(self, category):
  10.  
  11.         dict_store_session_query = {'person_gender':               lambda: Session.query(PERSON_GENDER),
  12.                                      'person_nationality':          lambda: Session.query(PERSON_NATIONALITY),
  13.                                      'person_salutation':           lambda: Session.query(PERSON_SALUTATION),
  14.                                      'person_title':                lambda: Session.query(PERSON_TITLE),
  15.                                      'person_hair_color':           lambda: Session.query(PERSON_HAIR_COLOR),
  16.                                      'person_eye_color':            lambda: Session.query(PERSON_EYE_COLOR),
  17.                                      'person_religion':             lambda: Session.query(PERSON_RELIGION),
  18.                                      'person_relationship_status':  lambda: Session.query(PERSON_RELATIONSHIP_STATUS)}
  19.  
  20.         try:
  21.  
  22.             for record in dict_store_session_query[category]():
  23.                 if category == 'person_gender':
  24.                     yield record.id, record.gender
  25.                 if category == 'person_nationality':
  26.                     yield record.id, record.nationality
  27.                 if category == 'person_salutation':
  28.                     yield record.id, record.salutation
  29.                 if category == 'person_title':
  30.                     yield record.id, record.title
  31.                 if category == 'person_hair_color':
  32.                     yield record.id, record.hair_color
  33.                 if category == 'person_eye_color':
  34.                     yield record.id, record.eye_color
  35.                 if category == 'person_religion':
  36.                     yield record.id, record.religion
  37.                 if category == 'person_relationship_status':
  38.                     yield record.id, record.relationship_status
  39.             Session.commit()
  40.  
  41.         except Exception:
  42.             Session.rollback()


worker.py
Hier habe ich die scoped_session() und sessionmaker() nicht importiert, brauchen wir nicht.
  1. import sys
  2.  
  3. from PyQt4.QtCore import QTimer, QObject, pyqtSignal
  4. from managed_data_manipulation import MasterDataManipulation
  5. class Worker(QObject):
  6.  
  7.     notify_item = pyqtSignal(object)
  8.     finish_progress = pyqtSignal()
  9.    
  10.     def __init__(self, category=None, parent=None):
  11.         QObject.__init__(self, parent)
  12.  
  13.         self.category=category
  14.        
  15.     def init_object(self):
  16.         master_data_manipulation = MasterDataManipulation()
  17.         query_data=master_data_manipulation.select_all
  18.         self._element = query_data(self.category)
  19.  
  20.         self.timer = QTimer()
  21.  
  22.         self.timer.setSingleShot(False)
  23.         self.timer.setInterval(100)
  24.         self.timer.timeout.connect(self.increment)
  25.         self.timer.start()
  26.            
  27.     def increment(self):
  28.  
  29.         try:
  30.             self.notify_item.emit(next(self._element))
  31.  
  32.         except StopIteration:
  33.  
  34.             self.finish_progress.emit()
  35.  
  36.             self.timer.stop()
  37.        
  38.     def stop(self):
  39.  
  40.         self.timer.stop()


my_custom_dialog.py
Ich habe auf die QComboBox()-Objekte verzichtet. Ich nehme die Print-Anweisungen. Die on_login()-Methode simuliert das Anmelden an den Datenbank. In der on_start_select_all()-Methode starte ich die ganzen Threads.

  1. import sys
  2.  
  3. from PyQt4.QtCore import pyqtSignal, QThread
  4.  
  5. from PyQt4.QtGui import QDialog, QLabel, QPushButton, \
  6.      QApplication, QVBoxLayout
  7.  
  8. from sqlalchemy.exc import SQLAlchemyError
  9.  
  10. from defined_session import *
  11.  
  12. from worker import Worker
  13. from managed_engine import ManagedEngine
  14.  
  15.  
  16. class MyCustomDialog(QDialog):
  17.  
  18.     finish = pyqtSignal()
  19.  
  20.     def __init__(self, parent=None):
  21.         QDialog.__init__(self, parent)
  22.  
  23.         self.managed_engine = None
  24.        
  25.         layout = QVBoxLayout(self)
  26.        
  27.         self.pushButton_start = QPushButton("Start", self)
  28.         self.pushButton_login = QPushButton("LogIn", self)
  29.         self.pushButton_stopp = QPushButton("Stopp", self)
  30.         self.pushButton_close = QPushButton("Close", self)
  31.  
  32.         layout.addWidget(self.pushButton_login)
  33.         layout.addWidget(self.pushButton_start)
  34.         layout.addWidget(self.pushButton_stopp)
  35.         layout.addWidget(self.pushButton_close)
  36.  
  37.         self.pushButton_login.clicked.connect(self.on_login)
  38.         self.pushButton_start.clicked.connect(self.on_start_select_all)
  39.         self.pushButton_stopp.clicked.connect(self.on_finish)
  40.         self.pushButton_close.clicked.connect(self.close)
  41.  
  42.     def print_populate(self, i):
  43.         print "i", i
  44.  
  45.     def on_login(self):
  46.         dbms ="mysql"
  47.         dbdriver="pymysql"
  48.         dbuser="root"
  49.         dbuser_pwd="xxx"
  50.         db_server_host="localhost"
  51.         dbport=3306
  52.         db_name="test"
  53.         echo_verbose=True
  54.  
  55.         try:
  56.  
  57.             managed_engine = ManagedEngine(dbms=dbms, dbdriver=dbdriver,
  58.                                                dbuser=dbuser, dbuser_pwd=dbuser_pwd,
  59.                                                db_server_host=db_server_host,
  60.                                                dbport=dbport, db_name=db_name,
  61.                                                echo_verbose=echo_verbose)
  62.            
  63.             Session.configure(bind=managed_engine._engine)
  64.            
  65.         except SQLAlchemyError as Err:
  66.             print "Err", Err
  67.  
  68.     def on_start_select_all(self):
  69.         category_list = [
  70.                     "person_salutation", "person_title",  
  71.                     "person_gender","person_religion",
  72.                     "person_eye_color", "person_hair_color",
  73.                     "person_relationship_status",
  74.                     "person_nationality"
  75.                        ]
  76.         for category in category_list:
  77.             self.on_start_thread_tasks(category=category)
  78.        
  79.     def on_start_thread_tasks(self, category=None):        
  80.          task_thread = QThread(self)
  81.          
  82.          task_thread.work = Worker(category=category)
  83.          task_thread.work.moveToThread(task_thread)
  84.          
  85.          task_thread.work.notify_item.connect(self.print_populate)
  86.          task_thread.work.finish_progress.connect(task_thread.quit)
  87.  
  88.          self.finish.connect(task_thread.work.stop)
  89.          
  90.          task_thread.started.connect(task_thread.work.init_object)
  91.          task_thread.finished.connect(task_thread.deleteLater)
  92.          task_thread.start()
  93.  
  94.     def on_finish(self):
  95.          self.finish.emit()
  96.      
  97. def main():
  98.     app = QApplication(sys.argv)
  99.     window = MyCustomDialog()
  100.     window.resize(600, 400)
  101.     window.show()
  102.     sys.exit(app.exec_())
  103.  
  104. if __name__ == "__main__":
  105.     main()
  106.  
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 20:39

Damit man nicht alles kopieren muss, habe ich dieses Programm auf BitBucket hochgeladen: https://bitbucket.org/Xenophyl/sqlalchemy-and-qthread/src
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 21:06

Zwischenergebnis: Wenn ich in der MyCustomDialog()-Klasse die on_start_select_all()-Methode, nach erfolgreicher Anmeldung an die Datenbank, mehrmals ausführe gibt es schon Probleme. Beim ersten Mal wird alles wunderbar ausgeführt. Beim Zweiten Mal werden mal sporadisch ein paar Datensätze unterschlagen und beim dritten Mal werden gar keine Daten erst abgefragt. Wenn ich eine Weile warte, und ein viertes Male versuche, dann kann es passieren, dass alle Daten geholt werden.
Benutzeravatar
__deets__
User
Beiträge: 2140
Registriert: Mittwoch 14. Oktober 2015, 14:29

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon __deets__ » Montag 14. August 2017, 21:43

So, das ist mein letzter Beitrag hier, weil ich meinen Punkt ausreichend belegt habe, und du augenscheinlich nicht in der Lage bist, dich mal wirklich mit einem kleinen Beispiel zu beschaefigen, sondern stattdessen immer riesen Menge an Code schreibst bei denen die Problem gottweisswo liegen koennen.

Ich bekomme auf meinem System kein Qt4 mehr installiert (das ist *uralt* und nicht mehr maintained. Solltest du nicht mehr verwenden, aber auch da halte ich jetzt lieber nicht die Luft an...), darum ist das PyQt5. Und was soll ich sagen? Es funktioniert wie angepriesen, man kann wunderbar nebenlaeufig Queries fahren etc.

  1. import sys
  2. import time
  3. import random
  4. import threading
  5.  
  6. from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
  7. from PyQt5.QtCore import QThread, QObject
  8.  
  9. from sqlalchemy import create_engine
  10. from sqlalchemy.orm import scoped_session
  11. from sqlalchemy.orm import sessionmaker
  12.  
  13.  
  14. session_factory = sessionmaker()
  15. Session = scoped_session(session_factory)
  16.  
  17.  
  18. def execute_db_query():
  19.     try:
  20.         res = Session.execute("SELECT * FROM test LIMIT 100")
  21.         for row in res:
  22.             print(threading.currentThread().getName(), row)
  23.             time.sleep(.1)
  24.     except:
  25.         Session.rollback()
  26.         print(sys.exc_info())
  27.     else:
  28.         Session.commit()
  29.  
  30.  
  31. class Worker(QObject):
  32.  
  33.     def work(self):
  34.         while True:
  35.             print("working...")
  36.             time.sleep(random.random() * 3)
  37.             execute_db_query()
  38.  
  39.  
  40. def setup_db():
  41.     eng = create_engine('sqlite:////tmp/test.db')
  42.     Session.configure(bind=eng)
  43.  
  44.     Session.execute('CREATE TABLE IF NOT EXISTS test ( data int)')
  45.     for i in range(1000):
  46.         Session.execute('INSERT INTO test (data) values (:param)',  { "param": i})
  47.  
  48.     Session.commit()
  49.  
  50. def create_worker_thread(parent=None):
  51.     task_thread = QThread(parent)
  52.     task_thread.worker = Worker()
  53.     task_thread.worker.moveToThread(task_thread)
  54.     task_thread.started.connect(task_thread.worker.work)
  55.     task_thread.finished.connect(task_thread.deleteLater)
  56.     return task_thread
  57.  
  58.  
  59. def main():
  60.     setup_db()
  61.     app = QApplication(sys.argv)
  62.  
  63.     w = QWidget()
  64.     w.resize(250, 150)
  65.     w.move(300, 300)
  66.     w.setWindowTitle('Simple')
  67.  
  68.     button = QPushButton(w)
  69.     button.clicked.connect(execute_db_query)
  70.     w.show()
  71.  
  72.     worker_thread = create_worker_thread()
  73.     worker_thread.start()
  74.     sys.exit(app.exec_())
  75.  
  76.  
  77. if __name__ == '__main__':
  78.     main()
Benutzeravatar
snafu
User
Beiträge: 5383
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » Montag 14. August 2017, 22:18

Sophus hat geschrieben:Wenn ich das wie __deets__ und snafu mache, dann müsste ich die ganzen Abfragen in meinen Hauptthread (in dem Falle wäre es die GUI-Klasse) verlegen.

Nein, ich habe für das genaue Gegenteil argumentiert: Die Session soll erst in der Worker-Funktion (Thread) angefasst werden. Irgendwie schreibe ich A und du verstehst Z. Das ist langsam frustrierend. :(
shcol (Repo | Doc | PyPi)
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 22:22

@snafu: Es ist nicht böse gemeint, aber was meinst, wie frustrierend das für mich ist? Erst arbeite ich mich deine Vorschläge durch, klappt nicht, dann die von __deets__ und irgendwie will das auch nicht klappen und dabei habe ich einen klar leserlichen Quelltext auf BitBucket bereits hingelegt :(
Benutzeravatar
snafu
User
Beiträge: 5383
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon snafu » Montag 14. August 2017, 22:26

Dass du erstmal alles ohne zusätzliche Threads schreibst, ist gar keine Option für dich? Dir fehlt bezüglich Threads offenbar noch einiges an Grundverständnis. Du hängst ja jetzt schon seit Tagen an dem Problem und doktorst mehr herum als du wirklich verstehst.
shcol (Repo | Doc | PyPi)
Benutzeravatar
Sophus
User
Beiträge: 1031
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Re: SQLAlchemy: Arbeiten mit mehreren QThreads?

Beitragvon Sophus » Montag 14. August 2017, 22:30

@snafu: Ich erstelle gerade extra mit deinem Namen eine Responsy auf meinem BitBucket, und lade mal deine Version hoch. Wie man mit Qthreads arbeitet, weiß ich, hoffentlich schon. Denn abseits von SQLAlchemy stellt für mich QThread kein Problem dar. Ich melde mit zurück, wenn die Responsy fertig ist.

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder