forked from openlp/openlp
Refactor threading to a centralised location which can keep track of all the threads
This commit is contained in:
parent
4b9905a4db
commit
1dda8f339f
@ -24,26 +24,28 @@ The :mod:`openlp.core.threading` module contains some common threading code
|
||||
"""
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
def run_thread(parent, worker, prefix='', auto_start=True):
|
||||
|
||||
def run_thread(worker, thread_name, can_start=True):
|
||||
"""
|
||||
Create a thread and assign a worker to it. This removes a lot of boilerplate code from the codebase.
|
||||
|
||||
:param object parent: The parent object so that the thread and worker are not orphaned.
|
||||
:param QObject worker: A QObject-based worker object which does the actual work.
|
||||
:param str prefix: A prefix to be applied to the attribute names.
|
||||
:param bool auto_start: Automatically start the thread. Defaults to True.
|
||||
:param str thread_name: The name of the thread, used to keep track of the thread.
|
||||
:param bool can_start: Start the thread. Defaults to True.
|
||||
"""
|
||||
# Set up attribute names
|
||||
thread_name = 'thread'
|
||||
worker_name = 'worker'
|
||||
if prefix:
|
||||
thread_name = '_'.join([prefix, thread_name])
|
||||
worker_name = '_'.join([prefix, worker_name])
|
||||
if not thread_name:
|
||||
raise ValueError('A thread_name is required when calling the "run_thread" function')
|
||||
main_window = Registry().get('main_window')
|
||||
if thread_name in main_window.threads:
|
||||
raise KeyError('A thread with the name "{}" has already been created, please use another'.format(thread_name))
|
||||
# Create the thread and add the thread and the worker to the parent
|
||||
thread = QtCore.QThread()
|
||||
setattr(parent, thread_name, thread)
|
||||
setattr(parent, worker_name, worker)
|
||||
main_window.threads[thread_name] = {
|
||||
'thread': thread,
|
||||
'worker': worker
|
||||
}
|
||||
# Move the worker into the thread's context
|
||||
worker.moveToThread(thread)
|
||||
# Connect slots and signals
|
||||
@ -51,5 +53,25 @@ def run_thread(parent, worker, prefix='', auto_start=True):
|
||||
worker.quit.connect(thread.quit)
|
||||
worker.quit.connect(worker.deleteLater)
|
||||
thread.finished.connect(thread.deleteLater)
|
||||
if auto_start:
|
||||
thread.finished.connect(make_remove_thread(thread_name))
|
||||
if can_start:
|
||||
thread.start()
|
||||
|
||||
|
||||
def make_remove_thread(thread_name):
|
||||
"""
|
||||
Create a function to remove the thread once the thread is finished.
|
||||
|
||||
:param str thread_name: The name of the thread which should be removed from the thread registry.
|
||||
:returns function: A function which will remove the thread from the thread registry.
|
||||
"""
|
||||
def remove_thread():
|
||||
"""
|
||||
Stop and remove a registered thread
|
||||
|
||||
:param str thread_name: The name of the thread to stop and remove
|
||||
"""
|
||||
main_window = Registry().get('main_window')
|
||||
if thread_name in main_window.threads:
|
||||
del main_window.threads[thread_name]
|
||||
return remove_thread
|
||||
|
@ -482,8 +482,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
"""
|
||||
super(MainWindow, self).__init__()
|
||||
Registry().register('main_window', self)
|
||||
self.version_thread = None
|
||||
self.version_worker = None
|
||||
self.threads = {}
|
||||
self.clipboard = self.application.clipboard()
|
||||
self.arguments = ''.join(self.application.args)
|
||||
# Set up settings sections for the main application (not for use by plugins).
|
||||
@ -1005,25 +1004,31 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||
if not self.application.is_event_loop_active:
|
||||
event.ignore()
|
||||
return
|
||||
# Sometimes the version thread hasn't finished, let's wait for it
|
||||
try:
|
||||
if self.version_thread and self.version_thread.isRunning():
|
||||
wait_dialog = QtWidgets.QProgressDialog('Waiting for some things to finish...', '', 0, 0, self)
|
||||
wait_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
wait_dialog.setAutoClose(False)
|
||||
wait_dialog.setCancelButton(None)
|
||||
wait_dialog.show()
|
||||
retry = 0
|
||||
while self.version_thread.isRunning() and retry < 50:
|
||||
self.application.processEvents()
|
||||
self.version_thread.wait(100)
|
||||
retry += 1
|
||||
if self.version_thread.isRunning():
|
||||
self.version_thread.terminate()
|
||||
wait_dialog.close()
|
||||
except RuntimeError:
|
||||
# Ignore the RuntimeError that is thrown when Qt has already deleted the C++ thread object
|
||||
pass
|
||||
# Sometimes the threads haven't finished, let's wait for them
|
||||
wait_dialog = QtWidgets.QProgressDialog('Waiting for some things to finish...', '', 0, 0, self)
|
||||
wait_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
wait_dialog.setAutoClose(False)
|
||||
wait_dialog.setCancelButton(None)
|
||||
wait_dialog.show()
|
||||
for thread_name in self.threads.keys():
|
||||
self.application.processEvents()
|
||||
thread = self.threads[thread_name]['thread']
|
||||
try:
|
||||
if thread and thread.isRunning():
|
||||
# If the thread is running, let's wait 5 seconds for it
|
||||
retry = 0
|
||||
while thread.isRunning() and retry < 50:
|
||||
# Make the GUI responsive while we wait
|
||||
self.application.processEvents()
|
||||
thread.wait(100)
|
||||
retry += 1
|
||||
if thread.isRunning():
|
||||
# If the thread is still running after 5 seconds, kill it
|
||||
thread.terminate()
|
||||
except RuntimeError:
|
||||
# Ignore the RuntimeError that is thrown when Qt has already deleted the C++ thread object
|
||||
pass
|
||||
wait_dialog.close()
|
||||
# If we just did a settings import, close without saving changes.
|
||||
if self.settings_imported:
|
||||
self.clean_up(False)
|
||||
|
@ -27,10 +27,10 @@ from time import sleep
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import is_win
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.mixins import RegistryProperties
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.threading import run_thread
|
||||
from openlp.plugins.songs.forms.songselectdialog import Ui_SongSelectDialog
|
||||
from openlp.plugins.songs.lib.songselect import SongSelectImport
|
||||
|
||||
@ -74,7 +74,7 @@ class SearchWorker(QtCore.QObject):
|
||||
self.found_song.emit(song)
|
||||
|
||||
|
||||
class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
|
||||
class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog, RegistryProperties):
|
||||
"""
|
||||
The :class:`SongSelectForm` class is the SongSelect dialog.
|
||||
"""
|
||||
@ -90,8 +90,6 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
|
||||
"""
|
||||
Initialise the SongSelectForm
|
||||
"""
|
||||
self.thread = None
|
||||
self.worker = None
|
||||
self.song_count = 0
|
||||
self.song = None
|
||||
self.set_progress_visible(False)
|
||||
@ -311,17 +309,11 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
|
||||
search_history = self.search_combobox.getItems()
|
||||
Settings().setValue(self.plugin.settings_section + '/songselect searches', '|'.join(search_history))
|
||||
# Create thread and run search
|
||||
self.thread = QtCore.QThread()
|
||||
self.worker = SearchWorker(self.song_select_importer, self.search_combobox.currentText())
|
||||
self.worker.moveToThread(self.thread)
|
||||
self.thread.started.connect(self.worker.start)
|
||||
self.worker.show_info.connect(self.on_search_show_info)
|
||||
self.worker.found_song.connect(self.on_search_found_song)
|
||||
self.worker.finished.connect(self.on_search_finished)
|
||||
self.worker.quit.connect(self.thread.quit)
|
||||
self.worker.quit.connect(self.worker.deleteLater)
|
||||
self.thread.finished.connect(self.thread.deleteLater)
|
||||
self.thread.start()
|
||||
worker = SearchWorker(self.song_select_importer, self.search_combobox.currentText())
|
||||
worker.show_info.connect(self.on_search_show_info)
|
||||
worker.found_song.connect(self.on_search_found_song)
|
||||
worker.finished.connect(self.on_search_finished)
|
||||
run_thread(worker, 'songselect')
|
||||
|
||||
def on_stop_button_clicked(self):
|
||||
"""
|
||||
@ -408,16 +400,3 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
|
||||
"""
|
||||
self.search_progress_bar.setVisible(is_visible)
|
||||
self.stop_button.setVisible(is_visible)
|
||||
|
||||
@property
|
||||
def application(self):
|
||||
"""
|
||||
Adds the openlp to the class dynamically.
|
||||
Windows needs to access the application in a dynamic manner.
|
||||
"""
|
||||
if is_win():
|
||||
return Registry().get('application')
|
||||
else:
|
||||
if not hasattr(self, '_application'):
|
||||
self._application = Registry().get('application')
|
||||
return self._application
|
||||
|
Loading…
Reference in New Issue
Block a user