@pypyshka

Как правильно вывести выполнение функций в отдельный поток, чтобы не блокировался интерфейс?

Добрый день. Есть небольшая программка на Python 3.4.4, которая парсит xml и сохраняет значения в БД. Так как xml-ки бывают довольно объёмные, то весь этот процесс занимает продолжительное время. Я попробовал выполнение функций с помощью threading - они выполняются, но интерфейс зависает:
def pars_xml():
....
def pars_xml_2():
....
app = QtGui.QApplication(sys.argv)
main_window = uic.loadUi("main.ui")
main_window.show()

thread_pars_xml = threading.Thread(target=pars_xml, name="pars_xml")
thread_pars_xml_2 = threading.Thread(target=pars_xml_2, name="pars_xml_2")
thread_pars_xml.start()
thread_pars_xml_2.start()
thread_pars_xml.join()
thread_pars_xml_2.join()

sys.exit(app.exec_())


Что можно в таком случае сделать?
Еще один момент: как лучше сделать выполнение этих функций с определенной периодичностью?
  • Вопрос задан
  • 473 просмотра
Решения вопроса 1
sergey-gornostaev
@sergey-gornostaev Куратор тега Python
Седой и строгий
Чтобы не залипал Qt-интерфейс следует воспользовать QThread

design.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>526</width>
    <height>373</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Threading Demo</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="label_log_list">
      <property name="text">
       <string>Отчёт:</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QListWidget" name="list_log"/>
    </item>
    <item>
     <widget class="QProgressBar" name="progress_bar">
      <property name="value">
       <number>0</number>
      </property>
     </widget>
    </item>
    <item>
     <layout class="QHBoxLayout" name="buttons_layout">
      <item>
       <widget class="QPushButton" name="btn_stop">
        <property name="enabled">
         <bool>false</bool>
        </property>
        <property name="text">
         <string>Стоп</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="btn_start">
        <property name="text">
         <string>Старт</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>


qthread_demo.py
from PyQt4 import QtGui, uic
from PyQt4.QtCore import QThread, SIGNAL
import sys
import time

class xmlParserThread(QThread):
    def __init__(self, xml):
        QThread.__init__(self)
        self.xml = xml

    def __del__(self):
        self.wait()

    def run(self):
        for x in range(25):
            self.emit(SIGNAL('notify_progress(QString)'), str(x))
            self.sleep(1)

        
class ThreadingDemo(QtGui.QMainWindow):
    def __init__(self):
        super(self.__class__, self).__init__()
        uic.loadUi('design.ui', self)
        self.btn_start.clicked.connect(self.start_getting_top_posts)

    def start_getting_top_posts(self):
        self.progress_bar.setMaximum(25)
        self.progress_bar.setValue(0)

        self.xml_parser_thread = xmlParserThread('some.xml')
        self.connect(self.xml_parser_thread, SIGNAL("notify_progress(QString)"), self.notify_progress)
        self.connect(self.xml_parser_thread, SIGNAL("finished()"), self.done)
        self.xml_parser_thread.start()

        self.btn_stop.setEnabled(True)
        self.btn_stop.clicked.connect(self.xml_parser_thread.terminate)
        self.btn_start.setEnabled(False)

    def notify_progress(self, counter):
        self.list_log.addItem(counter)
        self.progress_bar.setValue(self.progress_bar.value() + 1)

    def done(self):
        self.btn_stop.setEnabled(False)
        self.btn_start.setEnabled(True)
        self.progress_bar.setValue(0)
        QtGui.QMessageBox.information(self, "Done!", "Done parsing XML!")
    

def main():
    app = QtGui.QApplication(sys.argv)
    form = ThreadingDemo()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
mututunus
@mututunus
Backend developer (Python, Golang)
Читайте про QThread и QTimer.
Ответ написан
Комментировать
@pypyshka Автор вопроса
Спасибо за ответы. Вот что у меня получилось:
def pars_xml():
....

def pars_xml_2():
....

def do_it(sc):
    thread_pars_xml = threading.Thread(target=pars_xml, name="pars_xml")
    thread_pars_xml_2 = threading.Thread(target=pars_xml_2, name="pars_xml_2")
    thread_pars_xml.start()
    thread_pars_xml_2.start()
    thread_pars_xml.join()
    thread_pars_xml_2.join()
    sc.enter(60, 0, do_it, (sc,))

class MyThread(QtCore.QThread):
    def __init__(self, parent = None):
        QtCore.QThread.__init__(self, parent)

    def run(self):
        s = sched.scheduler(time.time, time.sleep)
        s.enter(60, 1, do_it, (s,))
        s.run()

app = QtGui.QApplication(sys.argv)
main_window = uic.loadUi("main.ui")
start_pars = MyThread()
start_pars .start()
main_window.show()
sys.exit(app.exec_())


После запуска программы через 1 секунду начинают выполняться функции по очереди с интервалом 60 секунд, в принципе, как я и хотел. Интерфейс при этом не залипает. Скажите, пожалуйста, правильна ли такая реализация, или можно проще сделать?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы