forked from npshub/mantid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
thread_model.py
155 lines (128 loc) · 5.72 KB
/
thread_model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
from qtpy import QtWidgets, QtCore
from qtpy.QtCore import Signal
from qtpy.QtCore import Slot
from Muon.GUI.Common.message_box import warning
class WorkerSignals(QtCore.QObject):
"""
Defines the signals available from a running ThreadModelWorker.
"""
started = Signal()
finished = Signal()
error = Signal(str)
cancel = Signal()
# Use this to start the worker
start = Signal()
class ThreadModelWorker(QtCore.QObject):
"""
The worker objects wraps the code that is to be run on the QThread, allowing us to "move" the instance of this class
into the thread (this prevents us needing to inherit from QThread, with is not a QObject, which causes many issues
for parenting widgets and managing the threading).
"""
def __init__(self, model):
super(ThreadModelWorker, self).__init__()
self.signals = WorkerSignals()
self.model = model
# This decorator is needed for the method to be successfully run on another thread.
# https://stackoverflow.com/questions/20752154/pyqt-connecting-a-signal-to-a-slot-to-start-a-background-operation/20818401#20818401
@Slot()
def run(self):
self.signals.started.emit()
try:
self.model.execute()
self.model.output()
except KeyboardInterrupt:
self.signals.cancel.emit()
except Exception as error:
try:
self.signals.error.emit(error.args[0])
except IndexError:
self.signals.error.emit("")
finally:
self.signals.finished.emit()
current_thread = QtCore.QThread.currentThread()
if current_thread is not None:
current_thread.quit()
class ThreadModel(QtWidgets.QWidget):
"""
A wrapper to allow threading with
the MaxEnt models.
"""
exceptionSignal = QtCore.Signal(str)
def __init__(self, model):
super(ThreadModel, self).__init__()
self.model = model
# The ThreadModelWorker wrapping the code to be executed
self._worker = ThreadModelWorker(self.model)
# The QThread instance on which the execution of the code will be performed
self._thread = QtCore.QThread(self)
# callbacks for the .started() and .finished() signals of the worker
self.start_slot = lambda: 0
self.end_slot = lambda: 0
self._exception_callback = self._default_exception_callback
self.check_model_has_correct_attributes()
def check_model_has_correct_attributes(self):
if hasattr(self.model, "execute") and hasattr(self.model, "output"):
return
raise AttributeError("Please ensure the model passed to ThreadModel has implemented"
" execute() and output() methods")
def setup_thread_and_start(self):
# create the ThreadModelWorker and connect its signals up
self._worker.signals.start.connect(self._worker.run)
self._worker.signals.started.connect(self.start_slot)
self._worker.signals.finished.connect(self.end_slot)
self._worker.signals.finished.connect(self.threadWrapperTearDown)
self._worker.signals.error.connect(self.warning)
# Create the thread and pass it the worker
self._thread.start()
self._worker.moveToThread(self._thread)
# start the worker code inside the thread
self._worker.signals.start.emit()
def start(self):
# keep this method to maintain consistency with older usages of the ThreadModel
self.setup_thread_and_start()
def warning(self, message):
self._exception_callback(message)
def join(self):
if self.exception is not None:
raise self.exception
def cancel(self):
self.model.cancel()
# if there is one set of inputs (1 alg)
def setInputs(self, inputs, runName):
self.model.setInputs(inputs, runName)
# if there are multiple inputs (alg>1)
def loadData(self, inputs):
if not hasattr(self.model, "loadData"):
raise AttributeError("The model passed to ThreadModel has not implemented"
" loadData() method, which it is attempting to call.")
self.model.loadData(inputs)
def threadWrapperSetUp(self,
on_thread_start_callback=lambda: 0,
on_thread_end_callback=lambda: 0,
on_thread_exception_callback=None):
assert hasattr(on_thread_start_callback, '__call__')
assert hasattr(on_thread_end_callback, '__call__')
self.start_slot = on_thread_start_callback
self.end_slot = on_thread_end_callback
if on_thread_exception_callback is not None:
assert hasattr(on_thread_exception_callback, '__call__')
self._exception_callback = on_thread_exception_callback
else:
self._exception_callback = self._default_exception_callback
def threadWrapperTearDown(self):
self._thread.wait()
self._thread = QtCore.QThread(self)
self._worker.signals.started.disconnect(self.start_slot)
self._worker.signals.finished.disconnect(self.end_slot)
self._worker.signals.finished.disconnect(self.threadWrapperTearDown)
self._worker.signals.error.disconnect(self.warning)
self.start_slot = lambda: 0
self.end_slot = lambda: 0
def _default_exception_callback(self, message):
warning(message)