Al.Sal
Al.Sal

Reputation: 984

Threading and Queuing with Functions

I've been having difficulty with multithreading and tkinter. slxl is a module I use that works and its functions do not return values (just to make sure there is no confusion.)

Here is code that has worked in the past:

    #relevant tkinter code
    def create_widgets():

        self.updatebttn = Button(self, text='Auto Update', command=self.spawnthread)
        self.updatebttn.grid(row=1, column=0, sticky=N)

        self.progressbar = Progressbar(self, orient='horizontal',
                                       length=300, mode='determinate')
        self.progressbar.grid(row=1, column=1, sticky=W)
        self.progressbar["maximum"] = (slxl.sizeFinder()) * 1.1


    def spawnthread(self):
        self.updatebttn.config(state="disabled")
        self.thread = ThreadedClient1(self.queue)
        self.thread.start()
        self.periodiccall()

    def periodiccall(self):
        if self.thread.is_alive():
            self.after(100, self.periodiccall)
            self.progressbar.step(500)
        else:
            self.updatebttn.config(state="active")
            self.progressbar.stop()

class ThreadedClient1(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue
    def run(self):
        time.sleep(1)
        importer = slxl.runImportAndCoordAdder()
        self.queue.put(importer)

The thing is, I would like ThreadedClient1 to be able to take the function it's putting into the queue, in this case slxl.runImportAndCoordAdder() as an argument, since I use ThreadedClient more than once with different functions (I have an identical use of it later on in the program only with a different function) and I don't like having ThreadedClient1 and ThreadedClient2, the only difference being importer = different functions.

I did attempt a solution with lambda as such:

def spawnthread(self):
    self.updatebttn.config(state="disabled")
    self.thread = ThreadedClient1(self.queue, lambda: slxl.runImportAndCoordAdder())
    self.thread.start()
    self.periodiccall()

#periodic call is the same

class ThreadedClient1(threading.Thread):
    def __init__(self, queue, fcn):
        threading.Thread.__init__(self)
        self.queue = queue
        self.fcn = fcn
    def run(self):
        time.sleep(1)
        self.queue.put(lambda: self.fcn)

This did not work, as it ignored the function I'm interested in, budged the progressbar a bit/disabled and enabled the button, then ended.

What am I doing wrong?

Edit: Problem solved. Another arose, though. I'd like to transfer spawnthread, periodiccall, and ThreadedClient to a widget template module I have and generalize them so I can use them easily. I did this:

def spawnthread(self, widget):
    widget.config(state="disabled")
    self.thread = ThreadedClient1(self.queue)
    self.thread.start()
    self.periodiccall()

Which worked, until I tried to generalize periodiccall as well.

    #...
        self.periodiccall(widget=widget)

    def periodiccall(self, widget=None):
        if self.thread.is_alive():
            self.after(100, self.periodiccall)
            self.progressbar.step(500)
            #print widget
        else:
            widget.config(state="active")
            self.progressbar.stop()

I popped in the print widget to see what was going on since I kept getting an attribute error. What appears to be happening is that the first time it runs though the function it knows what "widget" is and then it turns into in the None I wanted it to be by calling self.periodiccall in the after statement. At least, that's what I think is happening. How do I remove this error and make widget a variable in this recursive function?

Upvotes: 2

Views: 188

Answers (1)

A. Rodas
A. Rodas

Reputation: 20679

The problem is that you are not calling the function when you use it as an argument for ThreadedClient1:

class ThreadedClient1(threading.Thread):
    # ...
    def run(self):
        time.sleep(1)
        self.queue.put(lambda: self.fcn)

It is just a function that returns a reference to the lambda you pass as an argument, NOT the result of the call to slxl.runImportAndCoordAdder(). It should be:

class ThreadedClient1(threading.Thread):
    # ...
    def run(self):
        time.sleep(1)
        self.queue.put(self.fcn())

Or another solution with the reference directly (without lambdas):

def spawnthread(self):
    self.updatebttn.config(state="disabled")
    self.thread = ThreadedClient1(self.queue, slxl.runImportAndCoordAdder)
    self.thread.start()
    self.periodiccall()

class ThreadedClient1(threading.Thread):
    # ...
    def run(self):
        time.sleep(1)
        self.queue.put(self.fcn())

Upvotes: 1

Related Questions