S. Weber
S. Weber

Reputation: 53

Display count variable from background task in main task (Tkinter GUI)

I am trying to display a count variable from a background task in the main task which is my tkinter GUI. Why? I want to display that the long time taking background task is performing and later use this count variable to visualize it with a progress bar.

My problem is, that even when using a Queue, I am not able to display the count variable. Maybe I've got problems in understanding python and its behaviour with objects and/or threads.

import threading
import time
import Queue
import Tkinter as Tk
import Tkconstants as TkConst
from ScrolledText import ScrolledText
from tkFont import Font
import loop_simulation as loop

def on_after_elapsed():
    while True:
        try:
            v = dataQ.get(timeout=0.1)
        except:
            break
        scrText.insert(TkConst.END, str(v))     # "value=%d\n" % v
        scrText.see(TkConst.END)
        scrText.update()
    top.after(100, on_after_elapsed)

def thread_proc1():
    x = -1
    dataQ.put(x)
    x = loop.loop_simulation().start_counting()
    # th_proc = threading.Thread(target=x.start_counting())
    # th_proc.start()

    for i in range(5):
        for j in range(20):
            dataQ.put(x.get_i())
            time.sleep(0.1)
            # x += 1
        time.sleep(0.5)
    dataQ.put(x.get_i())

top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
f = Font(family='Courier New', size=12)
scrText = ScrolledText(master=top, height=20, width=120, font=f)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15, expand=True)

th = threading.Thread(target=thread_proc1)
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
th.join()

In thread_proc1() I want to get the value of the counter of background task. This is the background task:

import time

class loop_simulation:

    def __init__(self):
        self.j = 0
        # self.start_counting()

    def start_counting(self):
        for i in range(0, 1000000):
            self.j = i
            time.sleep(0.5)

    def get_i(self):
        return str(self.j)

Upvotes: 3

Views: 741

Answers (1)

martineau
martineau

Reputation: 123541

The reason the count variable isn't being displayed is due to the

    x = loop.loop_simulation().start_counting()

statement in thread_proc1(). This creates a loop_simulation instance and calls its start_counting() method. However, other than already inserting a -1 into the dataQ, thread_proc1() doesn't do anything else until start_counting() returns, which won't be for a long time (500K seconds).

Meanwhile, the rest of your script is running and displaying only that initial -1 that was put in.

Also note that if start_counting() ever did return, its value of None is going to be assigned to x which later code attempts to use with: x.get_i().

Below is reworking of your code that fixes these issues and also follows the PEP 8 - Style Guide for Python Code more closely. To avoid the main problem of calling start_counting(), I changed your loop_simulation class into a subclass of threading.Thread and renamed it LoopSimulation, and create an instance of it in thread_proc1, so there are now two background threads in addition to the main one handling the tkinter-based GUI.

import loop_simulation as loop
from ScrolledText import ScrolledText
import threading
import Tkinter as Tk
import Tkconstants as TkConst
from tkFont import Font
import time
import Queue

def on_after_elapsed():
    # removes all data currently in the queue and displays it in the text box
    while True:
        try:
            v = dataQ.get_nowait()
            scrText.insert(TkConst.END, str(v)+'\n')
            scrText.see(TkConst.END)
        except Queue.Empty:
            top.after(100, on_after_elapsed)
            break

def thread_proc1():
    dataQ.put(-1)
    ls = loop.LoopSimulation()  # slow background task Thread
    ls.start_counting()
    while ls.is_alive():  # background task still running?
        for i in range(5):
            for j in range(20):
                dataQ.put(ls.get_i())
                time.sleep(0.1)
            time.sleep(0.5)
    dataQ.put('background task finished')

top = Tk.Tk()
dataQ = Queue.Queue(maxsize=0)
font = Font(family='Courier New', size=12)
scrText = ScrolledText(top, height=20, width=120, font=font)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15,
             expand=TkConst.YES)
th = threading.Thread(target=thread_proc1)
th.daemon = True  # OK for main to exit even if thread is still running
th.start()
top.after(100, on_after_elapsed)
top.mainloop()

loop_simulation.py module:

import threading
import time

class LoopSimulation(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.daemon = True  # OK for main to exit even if instance still running
        self.lock = threading.Lock()
        self.j = 0

    start_counting = threading.Thread.start  # an alias for starting thread

    def run(self):
        for i in range(1000000):
            with self.lock:
                self.j = i
            time.sleep(0.5)

    def get_i(self):
        with self.lock:
            return self.j

Upvotes: 1

Related Questions