本文へ移動
cccskills
無料GitHub で公開

pyqt-threading

Use when handling PyQt/PySide6 threading - QThread patterns, QThreadPool/QRunnable, thread safety rules, moveToThread, Qt Concurrent, QTimer, testing threaded code, lifecycle management

インストール方法を見る

含まれるファイル(6)

  • SKILL.md12.7 KB
  • references/advanced-safety.md5.4 KB
  • references/lifecycle-cleanup.md3.2 KB
  • references/pool-patterns.md8.0 KB
  • references/qt-concurrent.md4.3 KB
  • references/testing.md8.5 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

PyQt Threading - Core Patterns

Thread Safety Rules

CRITICAL: Qt/PyQt is NOT thread-safe for UI operations. You MUST follow these rules:

  1. Never access widgets from worker threads - Only the main thread can modify UI
  2. Use signals for cross-thread communication - Emit signals from worker, connect to slots in main thread
  3. Use Qt.QueuedConnection for thread-safe signal delivery - AutoConnection handles this automatically
  4. Never block the main thread - Long operations will freeze the UI
# ❌ WRONG: Direct UI access from thread
class BadWorker(QThread):
    def run(self):
        # This will crash or cause undefined behavior!
        self.label.setText("Done")

# ✅ CORRECT: Use signals
class GoodWorker(QThread):
    finished = Signal(str)
    
    def run(self):
        result = self.process_data()
        self.finished.emit(result)  # Signal emitted, UI updated in main thread

QThread with Worker Object (Recommended Pattern)

The most flexible pattern separates the worker logic from thread lifecycle:

from PySide6.QtCore import QThread, Signal, QObject, Slot

class Worker(QObject):
    """Worker object that does the actual work."""
    finished = Signal(object)
    progress = Signal(int)
    error = Signal(str)
    
    def __init__(self, data):
        super().__init__()
        self.data = data
        self._is_cancelled = False
    
    @Slot()
    def process(self):
        """Main processing method called from thread."""
        try:
            for i, item in enumerate(self.data):
                if self._is_cancelled:
                    return
                
                # Simulate heavy work
                result = self.process_item(item)
                self.progress.emit(int((i + 1) / len(self.data) * 100))
            
            self.finished.emit({"status": "success", "count": len(self.data)})
        except Exception as e:
            self.error.emit(str(e))
    
    def cancel(self):
        self._is_cancelled = True
    
    def process_item(self, item):
        import time
        time.sleep(0.1)  # Simulate work
        return item * 2

class ThreadController(QObject):
    """Manages worker thread lifecycle."""
    def __init__(self):
        super().__init__()
        self.thread = None
        self.worker = None
    
    def start_work(self, data):
        # Create thread and worker
        self.thread = QThread()
        self.worker = Worker(data)
        
        # Move worker to thread
        self.worker.moveToThread(self.thread)
        
        # Connect signals
        self.worker.finished.connect(self.on_finished)
        self.worker.progress.connect(self.on_progress)
        self.worker.error.connect(self.on_error)
        
        # Thread lifecycle
        self.thread.started.connect(self.worker.process)
        self.thread.finished.connect(self.thread.deleteLater)
        
        # Start thread
        self.thread.start()
    
    def cancel_work(self):
        if self.worker:
            self.worker.cancel()
        if self.thread:
            self.thread.quit()
            self.thread.wait()
    
    @Slot()
    def on_finished(self, result):
        print(f"Work completed: {result}")
        self.cleanup()
    
    @Slot()
    def on_progress(self, percent):
        print(f"Progress: {percent}%")
    
    @Slot()
    def on_error(self, error):
        print(f"Error: {error}")
        self.cleanup()
    
    def cleanup(self):
        self.thread = None
        self.worker = None

moveToThread() Pattern - Worker Object Semantics

Correct Pattern: Worker + Thread Controller

The worker object pattern is the recommended approach for explicit thread control:

from PySide6.QtCore import QThread, Signal, QObject, Slot

class Worker(QObject):
    """Worker owns the work, controller owns the thread."""
    progress = Signal(int)
    finished = Signal(object)
    
    def __init__(self, data):
        super().__init__()
        self.data = data
        self._is_running = False
    
    @Slot()
    def process(self):
        """Main work method - called from thread."""
        self._is_running = True
        try:
            # Heavy computation here
            for i in range(100):
                self.progress.emit(i)
            self.finished.emit(None)
        finally:
            self._is_running = False

class ThreadController(QObject):
    """Controller owns thread and manages worker."""
    def __init__(self):
        super().__init__()
        self.thread = None
        self.worker = None
    
    def start_work(self, data):
        # Create NEW thread and worker
        self.thread = QThread()
        self.worker = Worker(data)
        
        # CRITICAL: Move worker TO thread
        self.worker.moveToThread(self.thread)
        
        # Worker lifetime tied to thread lifetime
        self.thread.started.connect(self.worker.process)
        self.thread.finished.connect(self.thread.quit)
        self.thread.finished.connect(self.thread.deleteLater)
        
        self.thread.start()

Ownership Semantics

ObjectOwnerLifetimeDeletion
WorkerThreadControllerUntil thread.quit() + wait()worker.deleteLater()
ThreadThreadControllerUntil deletedthread.deleteLater()
SignalsTheir parentUntil parent deletedAutomatic

Common Mistakes to Avoid

# ❌ WRONG: Worker outlives thread
controller = ThreadController()
controller.thread = QThread()
controller.worker = Worker()
controller.thread.moveToThread(controller.worker)  # Wrong direction!
controller.thread.start()
# Problem: Worker destroyed before thread finishes

# ❌ WRONG: Forgetting thread lifecycle
def start_work():
    thread = QThread()
    worker = Worker()
    worker.moveToThread(thread)
    thread.start()  # Never quit() or wait()!
# Problem: Zombie thread keeps running

# ✅ CORRECT: Proper ownership chain
controller = ThreadController()
controller.start_work(data)
# Later when done:
controller.thread.quit()
controller.thread.wait()
# Now delete: controller.worker.deleteLater()

Common Issues

IssueCauseSolution
UI freezesBlocking operation in main threadMove to worker thread
Crashes on widget accessAccessing UI from worker threadUse signals instead
Memory leaksThread not cleaned upUse deleteLater() and proper lifecycle
DeadlocksMultiple mutexes acquired in different orderAlways acquire in same order, use timeout
Race conditionsShared data without locksUse QMutex or atomic operations

Best Practices

  1. Always use signals for cross-thread communication - Direct widget access from threads causes crashes
  2. Keep worker objects thread-affinity aware - Never assume QObject is in main thread
  3. Clean up threads properly - Use deleteLater() and quit() + wait()
  4. Handle cancellation - Check flags periodically in long operations
  5. Use QThreadPool for parallel independent tasks - Default pool manages resource limits
  6. Use moveToThread() for explicit thread control - Worker object pattern is recommended
  7. Never use time.sleep() in main thread - Use QTimer or workers instead
  8. Keep locks short-lived - Only hold mutexes for critical section duration
  9. Use QReadWriteLock for read-heavy data - Multiple readers possible, single writer
  10. Monitor thread pool limits - Set maxThreadCount to prevent resource exhaustion

Common Pitfalls

IssueCauseSolution
UI crashes on widget accessWidget accessed from worker threadAlways use signals to update UI from main thread
DeadlockMultiple mutexes acquired in different orderAlways acquire in consistent order, use timeouts
Race conditionsShared data without locksUse QMutex, QAtomicPointer, or atomic operations
Memory leaksThreads not cleaned upUse deleteLater() on threads and workers
Thread not stoppingNo quit() + wait() sequenceAlways call thread.quit() then thread.wait()
Signals firing from wrong threadAutoConnection uses queued deliveryAutoConnection is correct - don't change
Re-entrancy issuesSignal handler calls slot recursivelyUse flags to track state changes
Resource exhaustionUnlimited thread pool threadsSet maxThreadCount on QThreadPool
Busy-wait loopsThread polling without sleepUse QTimer instead of polling
Lock not releasedException before unlockUse QMutexLocker for RAII-style cleanup

Top 5 Mistakes to Avoid

  1. Never access UI from worker threads - The most common crash cause

    # ❌ CRASH: Direct UI access
    def run(self):
        self.label.setText("Done")  # Crashes!
    
    # ✅ SAFE: Use signals
    def run(self):
        self.finished.emit("Done")  # UI updated in main thread
    
  2. Forgetting thread lifecycle management - Threads become zombies

    # ❌ BAD: No cleanup
    thread = QThread()
    thread.start()  # Never quit() or wait()
    
    # ✅ GOOD: Proper lifecycle
    thread.start()
    thread.quit()
    thread.wait()  # Wait for thread to finish
    
  3. Acquiring locks in wrong order - Deadlock

    # ❌ DEADLOCK
    def do_work(self):
        with lock_a:
            with lock_b:
                pass
    
    def do_other_work(self):
        with lock_b:  # Different order!
            with lock_a:
                pass
    
    # ✅ SAFE: Consistent ordering
    # Always acquire locks in same order (e.g., by ID)
    
  4. Using locks too long - Performance issues

    # ❌ BAD: Lock held for long operation
    with lock:
        result = heavy_computation()
    
    # ✅ GOOD: Hold lock only for data access
    with lock:
        data = self.shared_data
    result = heavy_computation(data)  # Outside lock
    
  5. Forgetting to check cancellation - Threads don't stop

    # ❌ BAD: Infinite loop
    def run(self):
        while True:
            do_work()
    
    # ✅ GOOD: Check cancellation flag
    def run(self):
        while not self._cancelled:
            if self._work_done():
                break
            do_work()
    

Deep Dives

For detailed coverage of advanced patterns, load these reference files:

  • Pool Patterns (references/pool-patterns.md) - QThread Subclass, QThreadPool/QRunnable, QTimer, Fire-and-Forget
  • Qt Concurrent (references/qt-concurrent.md) - Map/filter/reduce operations, progress tracking, simple background tasks
  • Lifecycle & Cleanup (references/lifecycle-cleanup.md) - Thread signals, proper cleanup, graceful shutdown
  • Advanced Safety (references/advanced-safety.md) - Thread-safe state, UI state races, resource exhaustion
  • Testing (references/testing.md) - pytest-qt patterns, signal testing, thread lifecycle testing, race condition tests

References

Official Documentation

Qt for Python (PySide6/PyQt6)

Community Resources

Advanced Topics

Testing

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

aiohttp

無料

Use when building Python async HTTP services or clients with aiohttp - web server routing, middleware, WebSocket, SSE, streaming, client sessions, pytest-aiohttp testing, or troubleshooting SSL and timeout issues

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

ast-grep

無料

Use when doing structural code search and rewriting - ast-grep linting, refactoring, multi-language patterns

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

Use when building GBA games with the BPCore Lua engine - entity, sprite and tilemap functions, SRAM save and load, link cable multiplayer protocol, camera and scrolling, or optimization patterns

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

celery

無料

Use when running background tasks with Celery - worker and broker configuration (Redis, RabbitMQ), task routing by name vs queue, chains/groups/chords, retry patterns (autoretry_for, retry_backoff), acks_late semantics, failure detection, and monitoring with Flower

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

django

無料

Use when building Django applications - security hardening, authentication and permissions, ORM optimization, PostgreSQL features, Django 6.0, migrations, testing, and ecosystem libraries

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

Use when customizing Django Admin - save_formset, get_search_results, formsets, queryset optimization, db_index, custom URLs

日本語の概要は準備中です。原文の説明を表示しています。

CodeAtCode/oss-ai-skills222026年10月9日 更新

CodeAtCode のスキルをすべて見る

このスキルの問題を報告する