Part D - Defining Custom Signals¶
Just like you can define a custom result type, you can also define custom
signals for your worker to emit. The signals that a worker can emit are stored in
the signals attribute.
By default, an AsyncWorker emits three kinds of signals:
started: Signifies that the worker has started performing it’s task (fired whenemit_start()is called)progress: Signifies that the worker has updated its progress. This usually gets connected to a function that displays progress to the user, possibly within aProgressBarWidgetfinished: Signifies that the worker has completed it’s task. This signal also passes along the results of the worker (see Part C)
In most cases, these signals are sufficient for the worker to fully communicate its state. However, it is sometimes useful for your worker to have additional signals on top of those described above. In this example, our worker will perform two distinct tasks sequentially, and it will fire a custom signal once the first task is completed.
First, let’s define the custom signal type. Just like custom results inherit from AsyncWorkerResults,
custom signals inherit from AsyncWorkerSignals. PySide6 signals can pass along any value type, but in
this example it will pass along a simple string:
1class CustomWorkerSignals(AsyncWorkerSignals):
2 part_1_complete_signal = Signal(str)
Now that the signals are defined, let’s take a look at this example’s worker:
1class CustomAsyncWorker(AsyncWorker):
2 def __init__(self, delay_seconds: int, cycles=4):
3 super(CustomAsyncWorker, self).__init__()
4 self.delay_seconds = delay_seconds
5 self.cycles = cycles
6 # Redefine the 'results' and 'signals' attributes
7 self.results = CustomWorkerResults()
8 self.signals = CustomWorkerSignals()
9
10 def do_part_1(self):
11 progress = 5
12 self.update_progress(progress, 'Starting Task')
13 for ii in range(self.cycles):
14 time.sleep(self.delay_seconds)
15 progress += 90 / (2 * self.cycles)
16 self.update_progress(progress, f'Progress message from part 1 #{ii + 1}')
17 self.results.custom_result_1 = 'result 1'
18
19 def do_part_2(self):
20 progress = 50
21 for ii in range(self.cycles):
22 time.sleep(self.delay_seconds)
23 progress += 90 / (2 * self.cycles)
24 self.update_progress(progress, f'Progress message from part 2 #{ii + 1}')
25 self.results.custom_result_2 = 'result 2'
26
27 def run(self):
28 self.emit_start()
29 self.do_part_1()
30 self.signals.part_1_complete_signal.emit('Part 1 Complete')
31 self.do_part_2()
32 self.complete()
Within the __init__ method, the signals attribute and the
results attributes are redefined (this example will use the same result type as
Part C). This worker has two tasks that are separated out into their own methods. Within
run(), we emit the started signal, perform the first task,
emit the custom part_1_complete signal, perform the second task, and finally emit the finished
signal.
Just like the progress and finished signals get
connected to callbacks, the custom signal needs to be connected to a callback as well. This callback receives a string
from the signal and prints it to the console:
1def custom_signal_callback(signal_value: str):
2 print(f'\nCustom Signal received! Value: {signal_value}\n')
Now that everything is defined, let’s see the full example:
1from PySide6.QtWidgets import QApplication
2from PySide6.QtCore import Signal
3from PySink import AsyncManager, AsyncWorker, AsyncWorkerProgress, AsyncWorkerResults, AsyncWorkerSignals
4import sys
5import time
6
7
8# Define a class representing your result type, storing result values as attributes
9class CustomWorkerResults(AsyncWorkerResults):
10 custom_result_1 = None
11 custom_result_2 = None
12
13
14# Define a class representing your signal type, storing signals as attributes
15class CustomWorkerSignals(AsyncWorkerSignals):
16 part_1_complete_signal = Signal(str)
17
18
19class CustomAsyncWorker(AsyncWorker):
20 def __init__(self, delay_seconds: int, cycles=4):
21 super(CustomAsyncWorker, self).__init__()
22 self.delay_seconds = delay_seconds
23 self.cycles = cycles
24 # Redefine the 'results' and 'signals' attributes
25 self.results = CustomWorkerResults()
26 self.signals = CustomWorkerSignals()
27
28 def do_part_1(self):
29 progress = 5
30 self.update_progress(progress, 'Starting Task')
31 for ii in range(self.cycles):
32 time.sleep(self.delay_seconds)
33 progress += 90 / (2 * self.cycles)
34 self.update_progress(progress, f'Progress message from part 1 #{ii + 1}')
35 self.results.custom_result_1 = 'result 1'
36
37 def do_part_2(self):
38 progress = 50
39 for ii in range(self.cycles):
40 time.sleep(self.delay_seconds)
41 progress += 90 / (2 * self.cycles)
42 self.update_progress(progress, f'Progress message from part 2 #{ii + 1}')
43 self.results.custom_result_2 = 'result 2'
44
45 def run(self):
46 self.emit_start()
47 self.do_part_1()
48 self.signals.part_1_complete_signal.emit('Part 1 Complete')
49 self.do_part_2()
50 self.complete()
51
52
53# Function to be called when the custom signal is emitted
54def custom_signal_callback(signal_value: str):
55 print(f'\nCustom Signal received! Value: {signal_value}\n')
56
57
58# Function to be called whenever a worker's task has started
59def worker_started_callback(worker_id: str):
60 print(f'Worker with id {worker_id} has started its task\n')
61
62
63# Function to be called whenever progress is updated
64def progress_callback(progress: AsyncWorkerProgress):
65 print(f'Progress Received, value: {progress.value}, message: {progress.message}')
66
67
68# Function to be called when the worker is finished. The results are now of type CustomWorkerResults.
69def completion_callback(results: CustomWorkerResults):
70 print(f'\nWorker Complete!')
71 print(f'\tErrors: {results.errors}')
72 print(f'\tWarnings: {results.warnings}')
73 print(f'\tResult Attribute 1: {results.custom_result_1}')
74 print(f'\tResult Attribute 2: {results.custom_result_2}')
75 sys.exit() # Exit the App event loop
76
77
78def run_main():
79 app = QApplication()
80 # Create the Async Manager
81 manager = AsyncManager()
82 # Create the Worker and pass in the necessary values
83 worker = CustomAsyncWorker(delay_seconds=1, cycles=3)
84 # Connect the Worker's signals to their callbacks
85 worker.signals.part_1_complete_signal.connect(custom_signal_callback)
86 worker.signals.started.connect(worker_started_callback)
87 worker.signals.progress.connect(progress_callback)
88 worker.signals.finished.connect(completion_callback)
89 # Start the Worker and App event loop
90 manager.start_worker(worker)
91 app.exec()
92
93
94run_main()
Running this script prints the following to the console:
1Worker with id cd5575e0-5ec2-4dd9-b15a-daae136c3528 has started its task
2
3Progress Received, value: 5, message: Starting Task
4Progress Received, value: 20.0, message: Progress message from part 1 #1
5Progress Received, value: 35.0, message: Progress message from part 1 #2
6Progress Received, value: 50.0, message: Progress message from part 1 #3
7
8Custom Signal received! Value: Part 1 Complete
9
10Progress Received, value: 65.0, message: Progress message from part 2 #1
11Progress Received, value: 80.0, message: Progress message from part 2 #2
12Progress Received, value: 95.0, message: Progress message from part 2 #3
13
14Worker Complete!
15 Errors: []
16 Warnings: []
17 Result Attribute 1: result 1
18 Result Attribute 2: result 2