4

question regarding this code: Multithreading PyQt applications with QThreadPool

from code below, how to pass additional parameters to execute_this_fn from oh_no function ?

if i'm passing directly like arguments:

worker = Worker(self.execute_this_fn(arg1, arg2))

and accepting as usual:

def execute_this_fn(self, progress_callback, a ,b):

I'm getting an error:

result = self.fn(*self.args, **self.kwargs) TypeError: execute_this_fn() got multiple values for argument 'progress_callback'

there even comment which is telling "to path any args and kwargs to run function" but what is the right syntax for that?

Executable code from the article:

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

import time
import traceback, sys


class WorkerSignals(QObject):
    '''
    Defines the signals available from a running worker thread.

    Supported signals are:

    finished
        No data

    error
        `tuple` (exctype, value, traceback.format_exc() )

    result
        `object` data returned from processing, anything

    progress
        `int` indicating % progress 

    '''
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(int)


class Worker(QRunnable):
    '''
    Worker thread

    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and 
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function

    '''

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

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()    

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress        

    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''

        # Retrieve args/kwargs here; and fire processing using them
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            self.signals.finished.emit()  # Done



class MainWindow(QMainWindow):


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

        self.counter = 0

        layout = QVBoxLayout()

        self.l = QLabel("Start")
        b = QPushButton("DANGER!")
        b.pressed.connect(self.oh_no)

        layout.addWidget(self.l)
        layout.addWidget(b)

        w = QWidget()
        w.setLayout(layout)

        self.setCentralWidget(w)

        self.show()

        self.threadpool = QThreadPool()
        print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())

        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.recurring_timer)
        self.timer.start()

    def progress_fn(self, n):
        print("%d%% done" % n)

    def execute_this_fn(self, progress_callback):
        for n in range(0, 5):
            time.sleep(1)
            progress_callback.emit(n*100/4)

        return "Done."

    def print_output(self, s):
        print(s)

    def thread_complete(self):
        print("THREAD COMPLETE!")

    def oh_no(self):
        # Pass the function to execute
        worker = Worker(self.execute_this_fn) # Any other args, kwargs are passed to the run function
        worker.signals.result.connect(self.print_output)
        worker.signals.finished.connect(self.thread_complete)
        worker.signals.progress.connect(self.progress_fn)

        # Execute
        self.threadpool.start(worker) 


    def recurring_timer(self):
        self.counter +=1
        self.l.setText("Counter: %d" % self.counter)


app = QApplication([])
window = MainWindow()
app.exec_()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Nick PV
  • 415
  • 6
  • 14
  • Okay do you understand the difference between using a ThreadPool and using a QThread? I ask because they are used for very different purposes – Dennis Jensen Oct 10 '19 at 17:06
  • provide a [MRE] – eyllanesc Oct 10 '19 at 17:10
  • @DennisJensen QThreadPool is handles queuing and execution for me but QThread require to indicate queuing and join threads ? – Nick PV Oct 10 '19 at 17:13
  • @eyllanesc (man u are really don't like me for some reason) minimal executable example u can see at attached link in the first sentence – Nick PV Oct 10 '19 at 17:15
  • @NickPV 1) Here I do not come to make social life so I avoid giving my opinion if someone likes me or not 2) By SO rules the MRE must be in the question and not depend on external resources because if the link you provide is broken question might not be understood. Do you understand my request now? – eyllanesc Oct 10 '19 at 17:18
  • @eyllanesc My apologize. Ok, I got u, now it's there. any thoughts regarding the question? – Nick PV Oct 10 '19 at 17:26
  • @eyllanesc Yes, perfect, it's working. Now I understand what this mean : self.kwargs["progress_callback"] = self.signals.progress , - that progress_callback belong to kwargs but at the same time it's a signal from function. Good. Thanks! – Nick PV Oct 10 '19 at 17:44

1 Answers1

4

If you review the docs of the Worker class we see that it indicates how to establish the additional arguments:

class Worker(QRunnable):
    """
    Worker thread

    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and 
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function
    # ...

In your case you must change to:

worker = Worker(self.execute_this_fn, arg1, arg2)

On the other hand, if the following part of the source code is observed:

# ...
self.kwargs["progress_callback"] = self.signals.progress
# ...

from which it follows that progress_callback is part of the kwargs that must be after the args so you must change your code to:

def execute_this_fn(self, a, b, progress_callback):
eyllanesc
  • 235,170
  • 19
  • 170
  • 241