<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - worker-thread</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/worker-thread.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2021-05-30T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>GUI Freezing on Second Run with QThread — Why your PyQt worker thread stops responding after the first click, and how to fix it</title><link href="https://www.pythonguis.com/faq/gui-freezing-for-second-run/" rel="alternate"/><published>2021-05-30T09:00:00+00:00</published><updated>2021-05-30T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2021-05-30:/faq/gui-freezing-for-second-run/</id><summary type="html">I created a GUI that uses a worker thread to run calculations and update a progress bar. It works fine for the first run, but if I change the parameters and click the Calculate button again, the GUI stops responding. What's going wrong?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I created a GUI that uses a worker thread to run calculations and update a progress bar. It works fine for the first run, but if I change the parameters and click the Calculate button again, the GUI stops responding. What's going wrong?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is one of the most common issues people run into when using &lt;code&gt;QThread&lt;/code&gt; with worker objects in PyQt. The good news is that once you understand the cause, the fix is straightforward.&lt;/p&gt;
&lt;h2 id="whats-causing-the-freeze"&gt;What's Causing the Freeze&lt;/h2&gt;
&lt;p&gt;The root of the problem is the use of &lt;code&gt;deleteLater()&lt;/code&gt; on the worker object after the first run completes. Here's the pattern that causes trouble:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.worker.finished.connect(self.worker_thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When the worker emits &lt;code&gt;finished&lt;/code&gt;, two things happen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The thread's event loop is told to quit.&lt;/li&gt;
&lt;li&gt;The worker object is scheduled for deletion.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After this, the worker no longer exists and the thread is no longer running. So when you click the Calculate button a second time and emit a signal to the worker, there's nothing on the other end to receive it. The signal goes nowhere, the GUI sits waiting for a response that will never come, and your application appears frozen.&lt;/p&gt;
&lt;p&gt;The same issue applies if you also call &lt;code&gt;deleteLater()&lt;/code&gt; on the thread itself:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.worker_thread.finished.connect(self.worker_thread.deleteLater)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;After the first run, both the thread and the worker are gone.&lt;/p&gt;
&lt;h2 id="the-fix-dont-delete-what-you-need-to-reuse"&gt;The Fix: Don't Delete What You Need to Reuse&lt;/h2&gt;
&lt;p&gt;If you want to reuse the same worker and thread across multiple button clicks, you should not delete them after each run. Instead, keep the thread running and simply let the worker sit idle between tasks.&lt;/p&gt;
&lt;p&gt;Remove these lines:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.worker.finished.connect(self.worker.deleteLater)
self.worker_thread.finished.connect(self.worker_thread.deleteLater)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You should also reconsider whether you need to quit the thread after each run. If the thread is going to be reused, quitting it means you'll need to restart it before sending the next task &amp;mdash; and restarting a &lt;code&gt;QThread&lt;/code&gt; that has already quit can be error-prone.&lt;/p&gt;
&lt;p&gt;A cleaner approach is to leave the thread running for the lifetime of the window, and only quit it when the window closes.&lt;/p&gt;
&lt;h2 id="a-clean-reusable-worker-pattern"&gt;A Clean Reusable Worker Pattern&lt;/h2&gt;
&lt;p&gt;Let's walk through a complete example that demonstrates a working, reusable worker thread setup. This avoids the freeze-on-second-click problem entirely.&lt;/p&gt;
&lt;h3&gt;Setting Up the Worker&lt;/h3&gt;
&lt;p&gt;The worker lives in a separate thread and does some work when it receives a signal. When it finishes, it emits &lt;code&gt;finished&lt;/code&gt; &amp;mdash; but we don't delete it or quit the thread. This approach uses &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signals and slots&lt;/a&gt; to communicate safely between threads.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
import time


class Worker(QObject):
    progress = pyqtSignal(int)
    result = pyqtSignal(str)
    finished = pyqtSignal()

    @pyqtSlot(str, str)
    def do_work(self, param1, param2):
        """Simulate a long-running calculation."""
        for i in range(1, 101):
            time.sleep(0.03)  # Simulate work
            self.progress.emit(i)

        self.result.emit(f"Done with {param1} and {param2}")
        self.finished.emit()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3&gt;Setting Up the Main Window&lt;/h3&gt;
&lt;p&gt;In the main window, we create the worker and thread once during &lt;code&gt;__init__&lt;/code&gt; and reuse them for every button click. The thread stays alive until the window is closed.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout,
    QPushButton, QProgressBar, QLabel, QLineEdit,
)
from PyQt6.QtCore import QThread, pyqtSignal


class MainWindow(QMainWindow):
    work_requested = pyqtSignal(str, str)

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Reusable Worker Thread")

        layout = QVBoxLayout()

        self.input1 = QLineEdit("alpha")
        self.input2 = QLineEdit("beta")
        self.button = QPushButton("Calculate")
        self.progress_bar = QProgressBar()
        self.status_label = QLabel("Ready")

        layout.addWidget(self.input1)
        layout.addWidget(self.input2)
        layout.addWidget(self.button)
        layout.addWidget(self.progress_bar)
        layout.addWidget(self.status_label)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # Set up the worker and thread.
        self.worker = Worker()
        self.worker_thread = QThread()
        self.worker.moveToThread(self.worker_thread)

        # Connect signals.
        self.work_requested.connect(self.worker.do_work)
        self.worker.progress.connect(self.progress_bar.setValue)
        self.worker.result.connect(self.on_result)
        self.worker.finished.connect(self.on_finished)

        # Start the thread. It will stay running, waiting for work.
        self.worker_thread.start()

        self.button.clicked.connect(self.start_calculation)

    def start_calculation(self):
        self.button.setEnabled(False)
        self.status_label.setText("Calculating...")
        self.progress_bar.setValue(0)

        param1 = self.input1.text()
        param2 = self.input2.text()
        self.work_requested.emit(param1, param2)

    def on_result(self, result_text):
        self.status_label.setText(result_text)

    def on_finished(self):
        self.button.setEnabled(True)

    def closeEvent(self, event):
        """Clean up the thread when the window closes."""
        self.worker_thread.quit()
        self.worker_thread.wait()
        super().closeEvent(event)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can click Calculate, wait for it to finish, change the inputs, and click Calculate again &amp;mdash; it works every time.&lt;/p&gt;
&lt;h2 id="why-this-works"&gt;Why This Works&lt;/h2&gt;
&lt;p&gt;The worker thread starts once and stays running. Its event loop is always active, waiting for incoming signals. Each time you emit &lt;code&gt;work_requested&lt;/code&gt;, the signal crosses the thread boundary, and the worker's &lt;code&gt;do_work&lt;/code&gt; slot is invoked in the worker thread. When the work finishes, &lt;code&gt;finished&lt;/code&gt; is emitted, re-enabling the button &amp;mdash; but the worker and thread remain intact, ready for the next task.&lt;/p&gt;
&lt;p&gt;There's no &lt;code&gt;deleteLater&lt;/code&gt;, no quitting and restarting the thread. The thread lives for the entire lifetime of the window, and is cleanly shut down in &lt;code&gt;closeEvent&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="what-if-you-really-need-a-fresh-worker-each-time"&gt;What If You Really Need a Fresh Worker Each Time?&lt;/h2&gt;
&lt;p&gt;Sometimes your worker holds state that you want to reset between runs, or you want a truly fresh start each time. In that case, you can create a new worker and thread for each run &amp;mdash; but you need to be careful to set everything up from scratch each time.&lt;/p&gt;
&lt;p&gt;Here's what that pattern looks like:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;def start_calculation(self):
    self.button.setEnabled(False)
    self.status_label.setText("Calculating...")
    self.progress_bar.setValue(0)

    # Create fresh worker and thread for each run.
    self.worker = Worker()
    self.worker_thread = QThread()
    self.worker.moveToThread(self.worker_thread)

    # Connect signals.
    self.work_requested.connect(self.worker.do_work)
    self.worker.progress.connect(self.progress_bar.setValue)
    self.worker.result.connect(self.on_result)
    self.worker.finished.connect(self.on_finished)

    # Clean up after this run completes.
    self.worker.finished.connect(self.worker_thread.quit)
    self.worker.finished.connect(self.worker.deleteLater)
    self.worker_thread.finished.connect(self.worker_thread.deleteLater)

    self.worker_thread.start()

    param1 = self.input1.text()
    param2 = self.input2.text()
    self.work_requested.emit(param1, param2)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this version, &lt;code&gt;deleteLater&lt;/code&gt; is fine because you're creating everything fresh on the next click. You won't try to reuse a deleted object.&lt;/p&gt;
&lt;p&gt;One thing to watch: if &lt;code&gt;work_requested&lt;/code&gt; is a persistent signal on the main window, connecting it in every call to &lt;code&gt;start_calculation&lt;/code&gt; will accumulate duplicate connections. You can avoid this by disconnecting the signal before reconnecting, or by passing the parameters directly through the thread setup rather than using a signal.&lt;/p&gt;
&lt;p&gt;If you're looking for an alternative threading approach that simplifies some of this setup, consider using &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;QThreadPool and QRunnable&lt;/a&gt; instead &amp;mdash; it handles thread lifecycle management for you.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;When to use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keep the thread running&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When you want to reuse the worker across multiple clicks. Simpler and more efficient.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Create fresh worker and thread&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;When you need a clean slate each time. Requires careful setup and teardown.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The persistent thread approach is generally easier to get right and is the recommended pattern for most cases. Keep the thread alive, keep the worker alive, and let signals do the work of &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-transmitting-extra-data-qt-signals/"&gt;passing data back and forth&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete Working Example&lt;/h2&gt;
&lt;p&gt;Here's the full, copy-and-run example using the persistent thread approach:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import time

from PyQt6.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QLineEdit,
    QMainWindow,
    QProgressBar,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


class Worker(QObject):
    progress = pyqtSignal(int)
    result = pyqtSignal(str)
    finished = pyqtSignal()

    @pyqtSlot(str, str)
    def do_work(self, param1, param2):
        """Simulate a long-running calculation."""
        for i in range(1, 101):
            time.sleep(0.03)
            self.progress.emit(i)

        self.result.emit(f"Done with {param1} and {param2}")
        self.finished.emit()


class MainWindow(QMainWindow):
    work_requested = pyqtSignal(str, str)

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Reusable Worker Thread")

        layout = QVBoxLayout()

        self.input1 = QLineEdit("alpha")
        self.input2 = QLineEdit("beta")
        self.button = QPushButton("Calculate")
        self.progress_bar = QProgressBar()
        self.status_label = QLabel("Ready")

        layout.addWidget(self.input1)
        layout.addWidget(self.input2)
        layout.addWidget(self.button)
        layout.addWidget(self.progress_bar)
        layout.addWidget(self.status_label)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # Set up the worker and thread once.
        self.worker = Worker()
        self.worker_thread = QThread()
        self.worker.moveToThread(self.worker_thread)

        # Connect signals.
        self.work_requested.connect(self.worker.do_work)
        self.worker.progress.connect(self.progress_bar.setValue)
        self.worker.result.connect(self.on_result)
        self.worker.finished.connect(self.on_finished)

        # Start the thread &amp;mdash; it stays running until the window closes.
        self.worker_thread.start()

        self.button.clicked.connect(self.start_calculation)

    def start_calculation(self):
        self.button.setEnabled(False)
        self.status_label.setText("Calculating...")
        self.progress_bar.setValue(0)

        param1 = self.input1.text()
        param2 = self.input2.text()
        self.work_requested.emit(param1, param2)

    def on_result(self, result_text):
        self.status_label.setText(result_text)

    def on_finished(self):
        self.button.setEnabled(True)

    def closeEvent(self, event):
        self.worker_thread.quit()
        self.worker_thread.wait()
        super().closeEvent(event)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this, click Calculate, let it finish, change the text inputs, and click Calculate again. It works reliably every time. The worker thread stays alive in the background, ready for the next task, and is only shut down when you close the window.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.pythonguis.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="multithreading"/><category term="qthread"/><category term="gui-freezing"/><category term="worker-thread"/><category term="signals-slots"/><category term="python"/><category term="qt"/><category term="qt6"/></entry></feed>