Holger
Holger

Reputation: 1

In a well-running multithreaded app, a defined callback will not be called. Why?

I have a program that starts five threads. Every single thread says 'Hello World!' to the console. At the end of a thread a callback function should be called. This outputs 'Goodby folks' to the console.

To show that threading works, the runtimes of each individual thread are measured and reported. For comparison, the total running time is measured and reported as well.

The total runtime corresponds to the runtime of the longest-running thread. But the output 'Goodby folks' is missing all five times.

Output looks like:


saying Hello World! took 0.126 s
saying Hello World! took 0.439 s
saying Hello World! took 0.645 s
saying Hello World! took 0.994 s
saying Hello World! took 1.469 s

total runtime is 1.479 s

Please, who can tell me, what's wrong here?

import random
import sys
import time
from typing import Callable

from PyQt5.QtCore import QCoreApplication, QThread, QObject, pyqtSignal


class MeasureRuntime:
    def __enter__(self):
        self.__start = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.__stop = time.perf_counter()
        self.elapsed = self.__stop - self.__start
        return True


class MyQtThreads(QObject):

    def __enter__(self):
        self.__running_threads: list = []
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.__waitForThreadsListEmpty()
        return True

    def __waitForThreadsListEmpty(self):
        while self.__running_threads:
            for thread in self.__running_threads:
                if thread.isFinished():
                    self.__running_threads.pop(self.__running_threads.index(thread))

    def doInThread(self, fn, *args, callback: Callable = None, **kwargs) -> int:
        thread = MyQtThread(fn, *args, **kwargs)
        if callback:
            thread.SIG_FINISHED.connect(callback)
        self.__running_threads.append(thread)
        thread.start()
        return id(thread)


class MyQtThread(QThread):
    SIG_FINISHED = pyqtSignal(object)

    def __init__(self, fn, *args, **kwargs):
        super().__init__()

        self.fn = fn
        self.args = args
        self.kwargs = kwargs

    def run(self):
        ret = self.fn(*self.args, **self.kwargs)
        result_info = {"thread_id": id(self), "args": ret}
        self.SIG_FINISHED.emit(result_info)


def say_hello():
    with MeasureRuntime() as rt:
        random.seed()
        time.sleep(random.random()*3)
        print(f'saying Hello World! took ', end='')
    print(f'{rt.elapsed:.3f} s')


def say_adieu(args):
    print('Goodby folks')


class Main(QThread):

    def __init__(self):
        super().__init__()

    def run(self):

        print()
        with MeasureRuntime() as rt:

            with MyQtThreads() as threads:
                for _ in range(5):
                    threads.doInThread(say_hello, callback=say_adieu)

        print(f'\ntotal runtime is {rt.elapsed:.3f} s')


if __name__ == '__main__':
    qcoreapp = QCoreApplication(sys.argv)
    thread = Main()
    thread.finished.connect(qcoreapp.exit)
    thread.start()
    qcoreapp.exec()

Upvotes: 0

Views: 48

Answers (0)

Related Questions