Skip to main content

WidgetsWorker

WidgetsWorker provides a robust threading solution for PyQt/PySide applications, allowing you to run background tasks without freezing the user interface. This module implements a worker pattern using QRunnable to handle concurrent operations efficiently within the Custom Widgets ecosystem.


Overview

The WidgetsWorker module enables seamless background task execution in your Qt applications:

  • Thread Management: Easy-to-use worker threads for background operations
  • Signal-Based Communication: Safe communication between worker threads and the main UI thread
  • Progress Tracking: Built-in progress reporting for long-running tasks
  • Error Handling: Comprehensive exception handling with proper error propagation
  • Thread Safety: Proper cleanup and interruption support

Core Components

WorkerSignals Class

Defines the signals available from a running worker thread:

class WorkerSignals(QObject):
finished = Signal() # Emitted when thread completes
error = Signal(tuple) # Emitted on error: (exctype, value, traceback)
result = Signal(object) # Emitted with processing result
progress = Signal(int) # Emitted with progress percentage

Worker Class

The main worker thread class that inherits from QRunnable:

class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['progress_callback'] = self.signals.progress
self.is_interrupted = False

def stop(self):
self.is_interrupted = True

WorkerResponse Class

Provides utility functions for handling worker responses with basic print functionality.


Basic Usage

Simple Background Task

from Custom_Widgets.WidgetsWorker import Worker
from qtpy.QtCore import QThreadPool

def long_running_task(progress_callback=None):
"""Example background task"""
for i in range(100):
if progress_callback:
progress_callback.emit(i + 1)
QThread.msleep(50) # Simulate work
return "Task completed"

class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.threadpool = QThreadPool()

def start_task(self):
worker = Worker(long_running_task)
worker.signals.result.connect(self.on_task_result)
worker.signals.progress.connect(self.on_task_progress)
worker.signals.finished.connect(self.on_task_finished)
self.threadpool.start(worker)

def on_task_result(self, result):
print(f"Result: {result}")

def on_task_progress(self, progress):
print(f"Progress: {progress}%")

def on_task_finished(self):
print("Task finished")

Worker with Arguments

def process_data(data_list, multiplier, progress_callback=None):
results = []
total = len(data_list)

for i, item in enumerate(data_list):
if progress_callback:
progress = int((i + 1) / total * 100)
progress_callback.emit(progress)

results.append(item * multiplier)
QThread.msleep(10)

return results

def start_processing(self):
data = [1, 2, 3, 4, 5]
worker = Worker(process_data, data, 10)
worker.signals.result.connect(self.on_data_processed)
self.threadpool.start(worker)

Error Handling

def potentially_failing_task(progress_callback):
# Some operation that might fail
if some_condition:
raise ValueError("Something went wrong!")
return "Success"

def start_risky_task(self):
worker = Worker(potentially_failing_task)
worker.signals.error.connect(self.handle_error)
worker.signals.result.connect(self.handle_success)
self.threadpool.start(worker)

def handle_error(self, error):
exctype, value, traceback_str = error
print(f"Error: {exctype.__name__}: {value}")

def handle_success(self, result):
print(f"Success: {result}")

Integration with Custom Widgets

With Progress Indicators

from Custom_Widgets import FormProgressIndicator

class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.progress_indicator = FormProgressIndicator()

def start_task_with_progress(self):
worker = Worker(self.long_operation)
worker.signals.progress.connect(self.progress_indicator.setValue)
worker.signals.finished.connect(self.progress_indicator.hide)
self.threadpool.start(worker)
self.progress_indicator.show()

With Theme Engine

from Custom_Widgets.QCustomTheme import QCustomTheme

class ThemedApp(QMainWindow):
def __init__(self):
super().__init__()
self.theme_engine = QCustomTheme()

def generate_theme_icons(self):
worker = Worker(self.theme_engine.generateNewIcons)
worker.signals.progress.connect(self.update_icon_progress)
worker.signals.finished.connect(self.theme_engine.applyIcons)
self.threadpool.start(worker)

Advanced Usage

Multiple Coordinated Workers

def start_parallel_tasks(self):
self.completed_tasks = 0
self.total_tasks = 3

for i in range(self.total_tasks):
worker = Worker(self.individual_task, i)
worker.signals.finished.connect(self.on_single_task_done)
self.threadpool.start(worker)

def on_single_task_done(self):
self.completed_tasks += 1
if self.completed_tasks == self.total_tasks:
print("All tasks completed!")

Interruptible Worker

def start_interruptible_task(self):
self.current_worker = Worker(self.long_operation)
self.current_worker.signals.finished.connect(self.on_worker_finished)
self.threadpool.start(self.current_worker)

def stop_current_task(self):
if hasattr(self, 'current_worker'):
self.current_worker.stop()

def long_operation(self, progress_callback):
for i in range(1000):
if self.current_worker.is_interrupted:
return "Operation interrupted"

if progress_callback:
progress_callback.emit(i // 10)

QThread.msleep(100)

return "Operation completed"

Best Practices

1. Always Handle Errors

worker = Worker(self.background_task)
worker.signals.error.connect(self.handle_worker_error)
worker.signals.finished.connect(self.cleanup_resources)

2. Use Progress Callbacks for Long Tasks

def background_task(self, progress_callback):
total_steps = 100
for step in range(total_steps):
if progress_callback:
progress = int((step + 1) / total_steps * 100)
progress_callback.emit(progress)
# Perform work...

3. Proper Resource Cleanup

def cleanup_resources(self):
# Clean up any resources used by workers
if hasattr(self, 'current_worker'):
self.current_worker.stop()

Key Features in Custom Widgets Context

  • Seamless Integration: Works perfectly with other Custom Widgets components
  • Non-blocking UI: Keeps your application responsive during operations
  • Automatic Cleanup: Workers clean up automatically when finished
  • Flexible Communication: Multiple signal types for different communication needs
  • Progress Tracking: Essential for user feedback during long operations

Example: File Processing with Progress

def process_files(self, file_list):
worker = Worker(self.process_file_batch, file_list)
worker.signals.progress.connect(self.ui.progressBar.setValue)
worker.signals.result.connect(self.on_files_processed)
worker.signals.error.connect(self.on_processing_error)
self.threadpool.start(worker)

def process_file_batch(self, file_list, progress_callback):
results = []
total_files = len(file_list)

for i, file_path in enumerate(file_list):
# Process each file
result = self.process_single_file(file_path)
results.append(result)

# Update progress
if progress_callback:
progress = int((i + 1) / total_files * 100)
progress_callback.emit(progress)

return results

The WidgetsWorker module is an essential tool for creating responsive Qt applications within the Custom Widgets framework, providing a simple yet powerful way to handle background operations while maintaining smooth user interaction.