Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating a tk ProgressBar from a multiprocess.proccess in python3

I have successfully created a threading example of a thread which can update a Progressbar as it goes. However doing the same thing with multiprocessing has so far eluded me. I'm beginning to wonder if it is possible to use tkinter in this way. Has anyone done this?

I am running on OS X 10.7. I know from looking around that different OS's may behave very differently, especially with multiprocessing and tkinter.

I have tried a producer which talks directly to the widget, through both namespaces and event.wait, and event.set. I have done the same thing with a producer talking to a consumer which is either a method or function which talks to the widget. All of these things successfully run, but do not update the widget visually. Although I have done a get() on the IntVar the widget is bound to and seen it change, both when using widget.step() and/or widget.set(). I have even tried running a separate tk() instance inside the sub process. Nothing updates the Progressbar.

Here is one of the simpler versions. The sub process is a method on an object that is a wrapper for the Progressbar widget. The tk GUI runs as the main process. I also find it a little odd that the widget does not get destroyed at the end of the loop, which is probably a clue I'm not understanding the implications of.

import multiprocessing
from tkinter import *
from tkinter import ttk
import time

root = Tk()

class main_window:

    def __init__(self):
        self.dialog_count = 0

        self.parent = root
        self.parent.title('multiprocessing progess bar')

        frame = ttk.Labelframe(self.parent)
        frame.pack(pady=10, padx=10)

        btn = ttk.Button(frame, text="Cancel")
        btn.bind("<Button-1>", self.cancel)
        btn.grid(row=0, column=1, pady=10)

        btn = ttk.Button(frame, text="progress_bar")
        btn.bind("<Button-1>", self.pbar)
        btn.grid(row=0, column=2, pady=10)

        self.parent.mainloop()

    def pbar(self, event):

        name="producer %d" % self.dialog_count
        self.dialog_count += 1

        pbar = pbar_dialog(self.parent, title=name)

        event = multiprocessing.Event()
        p = multiprocessing.Process(target=pbar.consumer, args=(None, event))

        p.start()



    def cancel(self, event):
       self.parent.destroy()



class pbar_dialog:

    toplevel=None
    pbar_count = 0

    def __init__(self, parent, ns=None, event=None, title=None, max=100):
        self.ns = ns
        self.pbar_value = IntVar()
        self.max = max

        pbar_dialog.pbar_count += 1
        self.pbar_value.set(0)


        if not pbar_dialog.toplevel:
            pbar_dialog.toplevel= Toplevel(parent)

        self.frame = ttk.Labelframe(pbar_dialog.toplevel, text=title)
        #self.frame.pack()
        self.pbar = ttk.Progressbar(self.frame, length=300, variable=self.pbar_value)
        self.pbar.grid(row=0, column=1, columnspan=2, padx=5, pady=5)

        btn = ttk.Button(self.frame, text="Cancel")
        btn.bind("<Button-1>", self.cancel)
        btn.grid(row=0, column=3, pady=10)
        self.frame.pack()


    def set(self,value):
        self.pbar_value.set(value)

    def step(self,increment=1):
        self.pbar.step(increment)
        print ("Current", self.pbar_value.get())

    def cancel(self, event):
       self.destroy()

    def destroy(self):
        self.frame.destroy()
        pbar_dialog.pbar_count -= 1
        if pbar_dialog.pbar_count == 0:
            pbar_dialog.toplevel.destroy()

    def consumer(self, ns, event): 
        for  i in range(21):
            #event.wait(2)
            self.step(5)
            #self.set(i)
            print("Consumer", i)
        self.destroy()



if __name__ == '__main__':
    main_window()

For contrast, here is the threading version which works perfectly.

import threading
from tkinter import *
from tkinter import ttk
import time

root = Tk()

class main_window:

    def __init__(self):
        self.dialog_count = 0

        self.parent = root
        self.parent.title('multiprocessing progess bar')

        frame = ttk.Labelframe(self.parent)
        frame.pack(pady=10, padx=10)

        btn = ttk.Button(frame, text="Cancel")
        btn.bind("<Button-1>", self.cancel)
        btn.grid(row=0, column=1, pady=10)

        btn = ttk.Button(frame, text="progress_bar")
        btn.bind("<Button-1>", self.pbar)
        btn.grid(row=0, column=2, pady=10)

        self.parent.mainloop()


    def producer(self, pbar):
        i=0
        while i < 101:
            time.sleep(1)
            pbar.step(1)
            i += 1
        pbar.destroy()


    def pbar(self, event):

        name="producer %d" % self.dialog_count
        self.dialog_count += 1

        pbar = pbar_dialog(self.parent, title=name)

        p = threading.Thread(name=name, target=self.producer, args=(pbar,))

        p.start()

        #p.join()


    def cancel(self, event):
       self.parent.destroy()



class pbar_dialog:

    toplevel=None
    pbar_count = 0

    def __init__(self, parent, ns=None, event=None, title=None, max=100):
        self.ns = ns
        self.pbar_value = IntVar()
        self.title = title
        self.max = max

        pbar_dialog.pbar_count += 1

        if not pbar_dialog.toplevel:
            pbar_dialog.toplevel= Toplevel(parent)

        self.frame = ttk.Labelframe(pbar_dialog.toplevel, text=title)
        #self.frame.pack()
        self.pbar = ttk.Progressbar(self.frame, length=300, variable=self.pbar_value)
        self.pbar.grid(row=0, column=1, columnspan=2, padx=5, pady=5)

        btn = ttk.Button(self.frame, text="Cancel")
        btn.bind("<Button-1>", self.cancel)
        btn.grid(row=0, column=3, pady=10)
        self.frame.pack()

        self.set(0)

    def set(self,value):
        self.pbar_value.set(value)

    def step(self,increment=1):
        self.pbar.step(increment)

    def cancel(self, event):
       self.destroy()

    def destroy(self):
        self.frame.destroy()
        pbar_dialog.pbar_count -= 1
        if pbar_dialog.pbar_count == 0:
            pbar_dialog.toplevel.destroy()
            pbar_dialog.toplevel = None

    def automatic(self, ns, event): 
        for i in range(1,100):
            self.step()

if __name__ == '__main__':
    main_window()
like image 278
user967953 Avatar asked Sep 27 '11 22:09

user967953


1 Answers

Doing something similar, I ended up having to use a combination of threads and processes - the GUI front end had two threads: one for tkinter, and one reading from a multiprocessing.Queue and calling gui.update() - then the back-end processes would write updates into that Queue

like image 89
Shish Avatar answered Nov 06 '22 09:11

Shish