multithreadingwxpythonappendtext

wxPython: CallAfter(AppendText,string) does not print entire string


I have a thread that loops receiving socket data and printing it:

def post(self):
    while True:
        try:
            data = pickle.loads(self.sock.recv(1024))
            print data[0] % tuple(data[1])
        except (socket.error, EOFError): 
            break

I then have a GUI that redirects stdout to a textctrl like so:

import wx
import sys
import threading

class Redirect: 
    def __init__(self, ctrl):
        self.out = ctrl
    def write(self, string): 
        wx.CallAfter(self.out.AppendText,string)

class GUI(wx.Frame):
    def __init__(self, parent):
        self.monitor = wx.TextCtrl(self, wx.ID_ANY, \
                               style = wx.TE_MULTILINE | wx.TE_READONLY)
        redir = Redirect(self.monitor)
        sys.stdout = redir

        self.sizer = BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.monitor, 1, wx.GROW | wx.ALL)
        self.SetSizer(self.sizer)
        self.Show(True)

if __name__ == "__main__":
    app = wx.App(False)
    frame = GUI(None)
    app.MainLoop()

I've also got a second textctrl that accepts input (left out for simplicity). The issue is that sometimes CallAfter(AppendText,string) is not printing out the entire string. It is very rare, but sometimes the print will just be stopped abruptly in the middle of the string, at which point the next string is printed (and the app continues printing the strings as they're received as if nothing happened).

I've no idea what's causing this behavior, I've tried to induce it by typing into the second textctrl to see if that's causing it, but even if I do nothing, these "partial prints" appear every now and then. What's going on?


Solution

  • This is not a real answer, just a way how to replicate the problem:

    import wx
    import sys
    import threading
    
    def post():
        a = 0
        for stop in range(100):
            a = (a + 1) % 10
            data = str(a) * 1000 + " <END>" 
            print(data)
        for i, line in enumerate(frame.monitor.Value.split("\n")[:-1]):
            if not line.endswith(" <END>"):
                print("Invalid line %d" % (i+1))
    
    
    class Redirect: 
        def __init__(self, ctrl):
            self.out = ctrl
    
        def write(self, string): 
            wx.CallAfter(self.out.AppendText, string)
    
    
    class GUI(wx.Frame):
        def __init__(self, parent):
            wx.Frame.__init__(self, parent)
            self.monitor = wx.TextCtrl(self, wx.ID_ANY, style = wx.TE_MULTILINE | wx.TE_READONLY)
            redir = Redirect(self.monitor)
            sys.stdout = redir
    
            self.sizer = wx.BoxSizer(wx.VERTICAL)
            self.sizer.Add(self.monitor, 1, wx.GROW | wx.ALL)
            self.SetSizer(self.sizer)
            self.Show(True)
    
    if __name__ == "__main__":
        app = wx.App(False)
        frame = GUI(None)
        t = threading.Thread(target=post)
        t.start()
        app.MainLoop()
    

    When ran on my setup, I get (usually, not always):

    Invalid line 30
    Invalid line 63
    

    I tried playing with delays and mutexes / locks, but it seem that I did not find why this happens yet.