Part A - Starting an AsyncWorker

Let’s start by using the AsyncWorker's default task to see the signals it emits and how to receive those signals by connecting them to callbacks. By default, the AsyncWorker will perform a demo task of counting to five with a delay of 1 second between counts. Generally a worker emits three kinds of signals, a started signal, a progress signal, and a finished signal (you can also define/emit custom signals, but more on that later).

The started signal indicates that a worker has started it’s long-running task. It contains the id of the worker which can be used to differentiate between active workers. Note that to emit this signal, the worker must call its emit_start() method at the top of run() (more on overriding the run() method in Part B). Below is a callback that will be triggered on the started signal:

def worker_started_callback(worker_id: str):
    print(f'Worker with id {worker_id} has started its task\n')

The progress signal passes along an AsyncWorkerProgress object containing a progress value, status message, and the worker's id. Here is a simple callback function that will print out the progress of an AsyncWorker:

def progress_callback(progress: AsyncWorkerProgress):
    print(f'Progress Received, value: {progress.value}, message: {progress.message}')

The callback receives the progress as a parameter. Within the callback, the progress value and message attributes are extracted and printed to the console.

Now let’s take a look at the completion callback. This is the function that gets called when the worker is done running it’s task. It gets connected to the worker’s finished signal and receives the worker’s results as an AsyncWorkerResults object:

def completion_callback(results: AsyncWorkerResults):
    print(f'\nWorker Complete!')
    print(f'\tWarnings: {results.warnings}')
    print(f'\tErrors: {results.errors}')
    print(f'\tResults: {results.results_dict}')
    sys.exit()  # Exit the App event loop

The results object contains the worker’s warnings and errors (it also contains the results of the worker, those will be explained in Part B). This completion callback prints out the warnings, errors, and results, then calls sys.exit() to end the App event Loop.

Now that the callbacks are taken care of, let’s look at how an AsyncWorker is started with an AsyncManager:

 1def run_main():
 2    # Create an instance of QApplication. This allows us to start a Qt event loop.
 3    app = QApplication()
 4    #   Create the Async Manager
 5    manager = AsyncManager()
 6    #   Create the Worker
 7    worker = AsyncWorker()
 8    #   Connect the Worker's signals to their callbacks
 9    worker.signals.started.connect(worker_started_callback)
10    worker.signals.progress.connect(progress_callback)
11    worker.signals.finished.connect(completion_callback)
12    #   Start the Worker
13    manager.start_worker(worker)
14    #   Start the App Event Loop
15    app.exec()

The general logic is as follows:

  1. Create an instance of AsyncManager

  2. Create an instance of the worker

  3. Connect the worker’s signals to their callbacks (line 9-11)

  4. Start the worker by passing it to the manager’s start_worker() method

This logic is wrapped in a QApplication so that it can run within a Qt event loop. Here’s what the full python script looks like:

 1from PySide6.QtWidgets import QApplication
 2from PySink import AsyncManager, AsyncWorker
 3from PySink import AsyncWorkerProgress, AsyncWorkerResults
 4import sys
 5
 6
 7# Function to be called whenever a worker's task has started
 8def worker_started_callback(worker_id: str):
 9    print(f'Worker with id {worker_id} has started its task\n')
10
11
12# Function to be called whenever progress is updated
13def progress_callback(progress: AsyncWorkerProgress):
14    print(f'Progress Received, value: {progress.value}, message: {progress.message}')
15
16
17# Function to be called when the worker is finished
18def completion_callback(results: AsyncWorkerResults):
19    print(f'\nWorker Complete!')
20    print(f'\tWarnings: {results.warnings}')
21    print(f'\tErrors: {results.errors}')
22    print(f'\tResults: {results.results_dict}')
23    sys.exit()  # Exit the App event loop
24
25
26def run_main():
27    # Create an instance of QApplication. This allows us to start a Qt event loop.
28    app = QApplication()
29    #   Create the Async Manager
30    manager = AsyncManager()
31    #   Create the Worker
32    worker = AsyncWorker()
33    #   Connect the Worker's signals to their callbacks
34    worker.signals.started.connect(worker_started_callback)
35    worker.signals.progress.connect(progress_callback)
36    worker.signals.finished.connect(completion_callback)
37    #   Start the Worker
38    manager.start_worker(worker)
39    #   Start the App Event Loop
40    app.exec()
41
42
43run_main()

After running the script, the following lines will be printed to the console as the worker runs:

 1Worker with id 88f864a0-ba66-4b73-9a9a-38437d7225ce has started its task
 2
 3Progress Received, value: 5, message: Starting
 4Progress Received, value: 23.0, message: Step 1
 5Progress Received, value: 41.0, message: Step 2
 6Progress Received, value: 59.0, message: Step 3
 7Progress Received, value: 77.0, message: Step 4
 8Progress Received, value: 95.0, message: Step 5
 9
10Worker Complete!
11    Warnings: []
12    Errors: []
13    Results: {'demo_result': 'Demo Result Value'}

As indicated in the console output, the worker first fired it’s started signal, intermittently fired its progress signal as it worked, then finally fired it’s finished signal when its task was complete. All of this was done in a background thread which frees up the UI thread to handle user input (if there was one present). In Example 1, we will see how to actually set up a full PySide Application with PySink, but before that let’s see how to customize an AsyncWorker in Part B - Defining Custom AsyncWorkers.