0

I started a Tkinter application but I'm with problems with buffering. I searched the solution but didn't find it.

Correlated links:

  1. Calling python script with subprocess popen and flushing the data
  2. Python C program subprocess hangs at "for line in iter"
  3. Python subprocess standard output to a variable

As an exemple, this app has two buttons: Start and Stop. When I press the button Start, the spammer.py is called as a subprocess and when I press the button Stop the program must be killed.

# spammer.py
import time

counter = 0
while counter < 40:  # This process will last 10 second maximum 
    print("counter = %d" % counter)
    counter += 1
    time.sleep(0.25)  # 4 messages/second

While the PrintNumbers.py is running, I want the spammer's output be storage in a variable inside the Tkinter to be used in realtime. But once I try to read the buffer with myprocess.stdout.readline, it stucks and it doesn't continue until the subprocess finish and as consequence for exemple I cannot click on the Stop button.

I read that the function is waiting for EOF to continue, and I tried to use tokens as shown here, and the function that should continue when it finds a or a \n, but it did not work.

The Tkinter exemple is bellow. After I click at the Start button, I instantly see the message Started reading stdout, and after 10 seconds it shows a lot of messages, while I wanted to show every message over time.

# PrintNumbers.py
import Tkinter as tk
import subprocess

class App:
    def __init__(self, root):
        self.root = root
        self.myprocess = None
        self.logmessages = []
        self.createButtons()
        self.timedUpdate()

    def createButtons(self):
        self.ButtonsFrame = tk.Frame(self.root, width=600, height=400)
        self.ButtonsFrame.pack()
        self.startbutton = tk.Button(self.ButtonsFrame, text="Start",
                                     command=self.__ClickOnStarButton)
        self.stopbutton = tk.Button(self.ButtonsFrame, text="Stop",
                                    command=self.__ClickOnStopButton)
        self.startbutton.pack()
        self.stopbutton.pack()
        self.startbutton["state"] = "normal"
        self.stopbutton["state"] = "disable"

    def __ClickOnStarButton(self):
        print("Click on Start Button")
        self.startbutton["state"] = "disable"
        self.stopbutton["state"] = "normal"
        self.startProcess()

    def __ClickOnStopButton(self):
        print("Click on Stop Button")
        self.startbutton["state"] = "normal"
        self.stopbutton["state"] = "disable"
        self.killProcess()
    
    def startProcess(self):
        command = "python spammer.py"
        self.myprocess = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=1)

    def killProcess(self):
        self.myprocess.terminate()
        self.myprocess.wait()
        self.myprocess = None

    def timedUpdate(self):
        if self.myprocess is not None:  # There's a process running
            self.getLogText()  # Will get the info from spammer.py
            self.treatOutput()  # Do whatever we want with the data
        root.after(200, self.timedUpdate)  # Every 0.2 seconds we will update
   
    def getLogText(self):
        if self.myprocess is None:  # There's no process running
            return
        # The problem is here
        print("Started reading stdout")
        for line in iter(self.myprocess.stdout.readline, ''):
            print("    Inside the loop. line = '%s'" % line)
            self.logmessages.append(line)
        print("Finished reading stdout")
        
    def treatOutput(self):
        # Any function that uses the spammer's output
        # it's here just to test
        while len(self.logmessages):
            line = self.logmessage.pop(0)
            line = line.replace("counter = ", "")
            mynumber = int(line)
            if mynumber % 3:
                print(mynumber)

if __name__ == "__main__":
    root = tk.Tk()
    app = App(root)
    root.mainloop()

How can I read the output without getting stuck? I'm still using python 2.7, and I don't know if it's the problem either.

Carlos Adir
  • 452
  • 3
  • 9
  • It works fine for me, i.e., prints messages over time (Python 3.8), however, since you are using that `for loop` it will block the main thread and the GUI will become unresponsive and you won't be able to stop the loop via the GUI, the good news is that the code works completely fine without modifications (except `import tkinter as tk` (the lower `t`)) on Python3 – Matiiss Oct 08 '21 at 09:40
  • Thank you! Nice to know that it works on Python 3.8. In python's 2.7 version the module's name is with upper ```T``` and now I want to know how to make it work on 2.7, cause I can't change the version :( Do you have any idea how to change the ```for``` to something like a ```while``` that checks if there's an spammer's output? – Carlos Adir Oct 08 '21 at 10:39
  • actually it really doesn't get stuck for you, it seems like it just buffers it all and then flushes, try adding `flush=True` in the spammer file: `print("counter = %d" % counter, flush=True)` (if that feature even exists, you may need to do it with `sys` module: [see here](https://stackoverflow.com/questions/230751/how-can-i-flush-the-output-of-the-print-function)) – Matiiss Oct 08 '21 at 13:27

0 Answers0