Python make a function always use a thread without calling thread.start()

Viewed 880

Essentially what I want to do is have a function that whenever it is called, it is executed on a separate thread. I do not want to call thread.start(), as this executes it immediately.

I have the following python script. I am using PyQt5 for my GUI. Whenever a button is pressed, I want to execute a long running function on a separate thread. here is the code:

from PyQt5 import QtWidgets, uic
import time


def longRunningFunction():
    time.sleep(10)


app = QtWidgets.QApplication([])
dlg = uic.loadUi("app.ui")

dlg.button_to_click.clicked.connect(longRunningFunction)

dlg.show()
app.exec()

When I add threads to the program, the function is executed on thread.start(). here is the code below, with threading added:

from PyQt5 import QtWidgets, uic
import time
import threading


def longRunningFunction():
    time.sleep(10)


app = QtWidgets.QApplication([])
dlg = uic.loadUi("app.ui")

dlg.button_to_click.clicked.connect(longRunningFunction)

thread = threading.Thread(target=longRunningFunction)
thread.start()

dlg.show()
app.exec()

What is the best way that I can make the function always use a separate thread when it is called, so that the GUI does not freeze up? This function will be called continuously while running, several times each minute. This function will also be much more complex than the example. It will use multiple methods from other custom made python scripts and also have a websocket connection running.

1 Answers

TLDR: Define a decorator that creates and starts a thread when the function is called.


Augmenting the functionality of functions/classes is commonly done using a decorator. This is a callable which receives a function/class, and either wraps or replaces it with the desired functionality. Decorators can be applied using @ syntax on definition of a function/class or via regular call later on.

A simple decorator for this use-case merely receives the callable (func) and wraps it in a new function which uses a thread to run the callable.

import threading
import functools


def threaded(func):
    """Decorator to automatically launch a function in a thread"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):  # replaces original function...
        # ...and launches the original in a thread
        thread = threading.Thread(target=func, args=args, kwargs=kwargs)
        thread.start()
        return thread
    return wrapper

This decorator can be applied when defining a new function using @ syntax:

@threaded
def long_running_function():
    time.sleep(10)
    print("Done")

long_running_function()
print("started")

If the function is already defined or must be used with and without threading, the decorator can also be applied by calling it:

dlg.button_to_click.clicked.connect(threaded(longRunningFunction))
Related