SQLAlchemy和QThread:多线程中的会话处理

时间:2017-08-09 01:38:35

标签: database python-2.7 sqlalchemy pyqt4 qthread

我有一个问题:我不知道如何使用会话,结合多线程,高效。那么你可以看到我使用scoped_session。它将透明地创建一个线程本地会话 - 所以它的线程安全。 我的下面和可执行示例有效,只要QTimer的间隔为1000,但如果将值设置为1,则存在一些问题。在GUI站点上,有8个QComboBox() - 对象,我将启动8个线程。在这个例子中,我使用一个表。当我运行此程序时,并非所有QComboBox() - 对象都被填充。有时只有一个QComboBox() - 对象空白或更多。另外,它偶尔发生,我被SQLAlchemy告知连接已关闭。为此,我有一条错误消息:

Traceback (most recent call last):
File "D:\Dan\Python\Xarphus\xarphus\subclass_master_data_load_data_item.py", line 151, in populate_item
self.populate_item_signal.emit(next(self._element))
File "D:\Dan\Python\Xarphus\xarphus\core\manage_data_manipulation_master_data.py", line 232, in select_all
yield record.id, record.relationship
File "D:\Dan\Python\Xarphus\xarphus\core\manage_db_connection.py", line 245, in __exit__
self.session.commit()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 906, in commit
self.transaction.commit()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 465, in commit
t[1].commit()
File "C:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 1632, in commit
self._do_commit()
File "C:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 1663, in _do_commit
self.connection._commit_impl()
File "C:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 726, in _commit_impl
self.connection._reset_agent is self.__transaction:
File "C:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 351, in connection
self._handle_dbapi_exception(e, None, None, None, None)
File "C:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 1405, in _handle_dbapi_exception
util.reraise(*exc_info)
File "C:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 349, in connection
return self._revalidate_connection()
File "C:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 429, in _revalidate_connection
raise exc.ResourceClosedError("This Connection is closed")
ResourceClosedError: This Connection is closed

而且,如果我多次运行我的程序,我会得到以下消息:

Traceback (most recent call last):
  File "C:\Users\Sophus\Desktop\ver_2_simple_problem.py", line 83, in init_object
    self._element = self.master_data_manipulation.select_all()
  File "C:\Users\Sophus\Desktop\ver_2_simple_problem.py", line 178, in __exit__
    self.session.commit()
  File "C:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 906, in commit
    self.transaction.commit()
  File "C:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 459, in commit
    self._assert_active(prepared_ok=True)
  File "C:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 258, in _assert_active
    "This session is in 'committed' state; no further "
sqlalchemy.exc.InvalidRequestError: This session is in 'committed' state; no further SQL can be emitted within this transaction.

这是我的示例代码:有关如何在多个线程中使用SQLAlchemy的更好,更优雅的源代码吗? Becaouse我不确定这是否正确。

    from PyQt4.QtCore import QObject, QThread, pyqtSignal, pyqtSlot, QTimer
from PyQt4.QtGui import QApplication, QPushButton, QVBoxLayout, QDialog, \
                        QComboBox, QLabel

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import Table, Column, Integer, String, MetaData

from traceback import format_exc
from sys import exc_info

''' setting up root class for declarative declaration '''
Base = declarative_base()

class PERSON_SALUTATION(Base):

    __tablename__ = "person_salutation"

    id = Column(Integer, primary_key=True)
    salutation = Column(String(50), nullable=False, unique=True)


class MasterDataManipulation(object):

    def __init__(self, session_object=None):

        self._session_scope = session_object

    def select_all(self):

        try:
            with self._session_scope as session:

                for record in session.query(PERSON_SALUTATION):

                    yield record.id, record.salutation

        except AttributeError:

            print "select all, desired_trace",  format_exc(exc_info())

        return

class Worker(QObject):

    finish_progress = pyqtSignal()
    populate_item_signal = pyqtSignal(object, object)

    def __init__(self,
                 combo_box=None,
                 query_data=None,
                 parent=None):
        QObject.__init__(self, parent)

        self.query_data = query_data
        self.combo_box=combo_box

        ''' Create attributes '''
        self._run_semaphore = 1

    def init_object(self):

        self._element = self.query_data()

        self.timer = QTimer()

        self.timer.setSingleShot(False)
        self.timer.setInterval(1)
        self.timer.timeout.connect(self.populate_item)
        self.timer.start()

    def populate_item(self):
        try:

            if self._run_semaphore == 0:

                self._run_semaphore = 1

                raise StopIteration

            else:

                self.populate_item_signal.emit(next(self._element), self.combo_box)

        except StopIteration:

            print "StopIteration is raised"

            self.timer.stop()

    def stop(self):
        self._run_semaphore == 0
        self.timer.stop()

class SessionScope(object):
    def __init__(self, dbms=None, dbdriver=None,
                 dbuser=None, dbuser_pwd=None,
                 db_server_host=None, dbport=None, db_name=None,
                 admin_database=None):

        self.dbms = dbms
        self.dbdriver = dbdriver
        self.dbuser = dbuser
        self.dbuser_pwd = dbuser_pwd
        self.db_server_host = db_server_host
        self.dbport = dbport
        self.db_name = db_name
        self.admin_database = admin_database

        url = '{}+{}://{}:{}@{}:{}/{}'.format(
           self.dbms, self.dbdriver, self.dbuser, self.dbuser_pwd, self.db_server_host, self.dbport, self.db_name)

        self._Engine = create_engine(url, encoding='utf8', echo=True)

        self.session = None

        self._session_factory = sessionmaker(bind=self._Engine)

        self._Session = scoped_session(sessionmaker(bind=self._Engine, expire_on_commit=False))

        ''' create tables '''
        Base.metadata.create_all(self._Engine)

    def __enter__(self):
        self.session = self._Session()
        return self.session

    def __exit__(self, exception, exc_value, traceback):

        try:
            if exception:

                self.session.rollback()
            else:

                self.session.commit()

        finally:

            self.session.close()

class MyCustomDialog(QDialog):

    finish = pyqtSignal()

    def __init__(self, scoped_session=None, parent=None):
        QDialog.__init__(self, parent)

        self._session_scope = scoped_session

        self._list_threads = []

        self.init_ui()
        self.start_all_selection()

    def init_ui(self):

        layout = QVBoxLayout(self)

        self.combo_person_title = QComboBox(self)
        self.combo_person_salutation = QComboBox(self)
        self.combo_person_gender = QComboBox(self)
        self.combo_person_religion = QComboBox(self)
        self.combo_person_relationship_status = QComboBox(self)
        self.combo_person_nationality = QComboBox(self)
        self.combo_person_eye_color = QComboBox(self)
        self.combo_person_hair_color = QComboBox(self)

        self.pushButton_populate_combo = QPushButton("Re-populate", self)
        self.pushButton_stopp = QPushButton("Stopp", self)
        self.pushButton_close = QPushButton("Close", self)
        layout.addWidget(self.combo_person_title)
        layout.addWidget(self.combo_person_salutation)
        layout.addWidget(self.combo_person_gender)
        layout.addWidget(self.combo_person_religion)
        layout.addWidget(self.combo_person_nationality)
        layout.addWidget(self.combo_person_relationship_status)
        layout.addWidget(self.combo_person_eye_color)
        layout.addWidget(self.combo_person_hair_color)
        layout.addWidget(self.pushButton_populate_combo)
        layout.addWidget(self.pushButton_stopp)
        layout.addWidget(self.pushButton_close)

        self.pushButton_stopp.clicked.connect(self.on_finish)
        self.pushButton_populate_combo.clicked.connect(self.start_all_selection)
        self.pushButton_close.clicked.connect(self.close)

    def start_all_selection(self):

        list_comboxes = self.findChildren(QComboBox)

        for combo_box in list_comboxes:
            combo_box.clear()
            self.start_thread(combo_box=combo_box)

    def fill_combo_boxt(self, item, combo_box):
        id, text = item
        combo_box.addItem(text)

    def on_label(self, i):
         self.label.setText("Result: {}".format(i))

    def start_thread(self, combo_box=None):
        master_data_manipulation = MasterDataManipulation(session_object=self._session_scope)
        query_data=master_data_manipulation.select_all

        task_thread = QThread(self)
        task_thread.work = Worker(query_data=query_data,
                                  combo_box=combo_box,)

        ''' We need to store threads '''
        self._list_threads.append(task_thread)  
        task_thread.work.moveToThread(task_thread)

        task_thread.work.populate_item_signal.connect(self.fill_combo_boxt)

        self.finish.connect(task_thread.work.stop)

        task_thread.started.connect(task_thread.work.init_object)

        task_thread.finished.connect(task_thread.deleteLater)

        ''' This will emit 'started' and start thread's event loop '''
        task_thread.start()

    @pyqtSlot()
    def abort_workers(self):
        self.finish.emit()
        for thread in self._list_threads:
            ''' this will quit **as soon as thread event loop unblocks** '''
            thread.quit()

            ''' so you need to wait for it to *actually* quit'''
            thread.wait()

    def on_finish(self):
         self.finish.emit()

    def closeEvent(self, event):
        ''' Re-implementaate to handle with created threads '''
        self.abort_workers()

        sys.exit()

def populate_database(sess=None):

    try:

        with sess as session:

            salutations = [PERSON_SALUTATION(salutation="Mister"),
                           PERSON_SALUTATION(salutation="Miss"),
                           PERSON_SALUTATION(salutation="Lady"),
                           PERSON_SALUTATION(salutation="Ma'am"),
                           PERSON_SALUTATION(salutation="Sir"),
                           PERSON_SALUTATION(salutation="Queen"),
                           PERSON_SALUTATION(salutation="Grandma"),]
            session.add_all(salutations)

            session.commit()

    except SQLAlchemyError:
        print "SQLAlchemyError", format_exc(exc_info())

def main():
    dbms = raw_input('Enter database type: ')
    dbdriver = raw_input('Enter database driver: ')
    dbuser = raw_input('Enter user name: ')
    dbuser_pwd = raw_input('Enter user password: ')
    db_server_host = raw_input('Enter server host: ')
    dbport = raw_input('Enter port: ')
    db_name = raw_input('Enter database name: ')

    try:
        ''' create_engine and scoped_session once per process (per database). '''
        session_scope = SessionScope(dbms = dbms,
                                     dbdriver = dbdriver,
                                     dbuser = dbuser,
                                     dbuser_pwd = dbuser_pwd,
                                     db_server_host = db_server_host,
                                     dbport = dbport,
                                     db_name = db_name)

        answer = raw_input('Do you want to populate database? Type yes or no: ')

        if answer.lower() == 'yes':
            populate_database(sess=session_scope)

        app = QApplication(sys.argv)
        window = MyCustomDialog(scoped_session = session_scope)
        window.show()
        sys.exit(app.exec_())
    except TypeError:

        print "ERROR", format_exc(exc_info())

if __name__ == "__main__":
    main()

0 个答案:

没有答案