File: tutorial-responsive-gui.rst

package info (click to toggle)
orange3 3.40.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,908 kB
  • sloc: python: 162,745; ansic: 622; makefile: 322; sh: 93; cpp: 77
file content (149 lines) | stat: -rw-r--r-- 5,065 bytes parent folder | download | duplicates (2)
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
##############
Responsive GUI
##############

And now for the hard part of making the widget responsive. We will do this
by offloading the learner evaluations into a separate thread.

First read up on `threading basics`_ in Qt and in particular the subject
of `threads and qobjects`_ and how they interact with the Qt's event loop.

.. _threading basics:
    http://doc.qt.io/qt-5/thread-basics.html

.. _threads and qobjects:
    http://doc.qt.io/qt-5/threads-qobject.html

We must also take special care that we can cancel/interrupt our task when
the user changes algorithm parameters or removes the widget from the canvas.
For that we use a strategy known as cooperative cancellation where we 'ask'
the pending task to stop executing (in the GUI thread), then in the worker
thread periodically check (at known predetermined points) whether we should
continue, and if not return early (in our case by raising an exception).


**********
Setting up
**********

We use :class:`Orange.widgets.utils.concurrent.ThreadExecutor` for thread
allocation/management (but could easily replace it with stdlib's
:class:`concurrent.futures.ThreadPoolExecutor`).

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-1
   :end-before: end-snippet-1


We will reorganize our code to make the learner evaluation an explicit
task as we will need to track its progress and state. For this we define
a `Task` class.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-2
   :end-before: end-snippet-2


In the widget's ``__init__`` we create an instance of the `ThreadExector`
and initialize the task field.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-3
   :end-before: end-snippet-3

All code snippets are from :download:`OWLearningCurveC.py <orange-demo/orangedemo/OWLearningCurveC.py>`.


***************************
Starting a task in a thread
***************************

In `handleNewSignals` we call `_update`.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-4
   :end-before: end-snippet-4


And finally the `_update` function (from :download:`OWLearningCurveC.py <orange-demo/orangedemo/OWLearningCurveC.py>`)
that will start/schedule all updates.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-5
   :end-before: end-snippet-5


At the start we cancel pending tasks if they are not yet completed. It is
important to do this, we cannot allow the widget to schedule tasks and
then just forget about them. Next we make some checks and return early if
there is nothing to be done.

Continue by setting up the learner evaluations as a partial function
capturing the necessary arguments:

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-6
   :end-before: end-snippet-6


Setup the task state and the communication between the main and worker thread.
The only `state` flowing from the GUI to the worker thread is the
`task.cancelled` field which is a simple trip wire causing the
`learning_curve`'s callback argument to raise an exception. In the other
direction we report the `percent` of work done.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-7
   :end-before: end-snippet-7

.. seealso::
   :func:`~Orange.widgets.widget.OWWidget.progressBarInit`,
   :func:`~Orange.widgets.widget.OWWidget.progressBarSet`,
   :func:`~Orange.widgets.widget.OWWidget.progressBarFinished`


Next, we submit the function to be run in a worker thread and instrument
a FutureWatcher instance to notify us when the task completes (via a
`_task_finished` slot).

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-8
   :end-before: end-snippet-8

For the above code to work, the `setProgressValue` needs defined as a pyqtSlot.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-progress
   :end-before: end-snippet-progress


******************
Collecting results
******************

In `_task_finished` (from :download:`OWLearningCurveC.py <orange-demo/orangedemo/OWLearningCurveC.py>`)
we handle the completed task (either success or failure) and then update the displayed score table.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-9
   :end-before: end-snippet-9


********
Stopping
********

Also of interest is the `cancel` method. Note that we also disconnect the
`_task_finished` slot so that `_task_finished` does not receive stale
results.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-10
   :end-before: end-snippet-10

We also use cancel in :func:`~Orange.widgets.widget.OWWidget.onDeleteWidget`
to stop if/when the widget is removed from the canvas.

.. literalinclude:: orange-demo/orangedemo/OWLearningCurveC.py
   :start-after: start-snippet-11
   :end-before: end-snippet-11