When to call thread.join in a GUI application - python

import wx
import json
import queue
from collections import namedtuple
import threading
class MyDialog(wx.Frame):
def __init__(self, parent, title):
self.no_resize = wx.DEFAULT_FRAME_STYLE & ~ (wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)
wx.Frame.__init__(self, parent, title=title, size=(500, 450),style = self.no_resize)
self.panel = wx.Panel(self, size=(250, 270))
self.emp_selection = wx.ComboBox(self.panel, -1, pos=(40, 50), size=(200,100))
self.start_read_thread()
#code to load other GUI components
self.Centre()
self.Show(True)
def read_employees(self, read_file):
list_of_emails = queue.Queue()
with open(read_file) as f_obj:
employees = json.load(f_obj)
list_of_emails = [empEmail for empEmail in employees.keys()]
wx.CallAfter(self.emp_selection.Append, list_of_emails)
def start_read_thread(self):
filename = 'employee.json'
empThread = threading.Thread(target=self.read_employees, args=(filename,))
empThread.start()
I have a GUI application that loads a combobox, and starts a thread to read some data and load it into the combobox. I don't want the read to block, so that the other GUI components can load.
After calling thread.start() when is it appropriate to call thread.join()? From my understanding, join() waits for the thread to complete, I don't want that, I want to start the thread and allow all the other components to load. Is it bad practice not to call join()

It is perfectly ok to not call join() if you don't need its functionality to wait for the thread to finish.
By the way: In the main thread of a GUI application (the thread automatically created at start in which all GUI things happen) it is bad practice to call any function that sleeps or waits for anything as the GUI doesn't react (freezes) while waiting.

Related

How to update the state of a Toggle Button after process completion?

I want to execute a task when a Toggle Button is clicked(on) and toggle it off after the task is completed, I execute the task in a new process because I don't want it to block the UI, event.GetEventObject().SetValue(False) seems to update correctly the value of the Toggle but it doesn't reflect on the UI.
from multiprocessing import Process
import time
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.toggle_button = wx.ToggleButton(self, wx.ID_ANY, "OK")
control = Control()
self.Bind(wx.EVT_TOGGLEBUTTON, control.action, self.toggle_button)
self.SetTitle("Update UI with a process")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.toggle_button, 0, 0, 0)
self.SetSizer(sizer)
self.Layout()
class Control():
def update_toggle(self, duration, event):
time.sleep(duration)
event.GetEventObject().SetValue(False)
print("Toggled")
def action(self, event):
if event.GetEventObject().GetValue():
self.update_toggle_process = Process(target = self.update_toggle,
args=(5, event,))
self.update_toggle_process.start()
else:
print("UnToggled")
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, wx.ID_ANY, "")
self.SetTopWindow(self.frame)
self.frame.Show()
return True
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
A call to event.GetEventObject().Update() or event.GetEventObject().Refresh() after changing the value of the Toggle doesn't seem to change anything.
EDIT:
If I use a Thread instead of Process, it works correctly, but I chose Process over Thread because I want the ability to kill it cleanly whenever I need to.
Python version: 3.7
WxPython version: 4.0.1
You must remember, that the your update_toggle runs in a new process. Simply put, it has got a copy of data, so if you call event.GetEventObject().SetValue(False) it happens in the new process, and the original one with the Window and Button won't know.
You must somehow pass a message from the new process to the original. I would suggest that the first thing you try is:
self.update_toggle_process.start()
self.update_toggle_process.join()
print("the process has finished")
This will block, but at least you will see if the "update_toggle_process" has finished and if this approach works. After that, there are a few possibilities:
Set up a times and periodically call self.update_toggle_process.is_alive()
Create a new thread, call the update_toggle_process.start() from it, and also join(). When finished, tell the main thread to toggle the button (remember that you may only manipulate the UI from the main thread in wx)
Maybe you do not need a new process, a thread will be enough
Look at the multiprocessing IPC

Exiting from a thread causes unresponsive GUI

This is a follow-up to my previous question, wxPython popup from calling imported function.
I was able to figure out a way to create a wxPython dialog window to determine if a thread my GUI called should continue its execution. What I did was simply create the dialog window in the thread itself. However, once I made the thread exit by clicking "no" in this popup, the close button in my GUI became unresponsive, and I couldn't close the GUI itself. Once again, your help is much appreciated!
GUI code:
import sys
import os
import re
import subprocess
import threading
import wx
import errno, os, stat, shutil
import extern_func
#this object redirects the external function output to the text box
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
#GUI code here
class progFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="functionGUI", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
panel = wx.Panel(self)
#more things....
self.closeButton = wx.Button(panel, wx.ID_OK, "Run", pos=(250, 300))
self.runButton = wx.Button(panel, wx.ID_OK, "Run", pos=(200, 300))
self.out=wx.TextCtrl(panel, style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY, pos = (300, 50), size=(500, 200))
#Run button event
self.Bind(wx.EVT_BUTTON, self.OnRun, self.runButton)
#close button event
self.Bind(wx.EVT_BUTTON, self.OnClose, self.closeButton)
#command prompt output to frame
redir=RedirectText(self.out)
sys.stdout=redir
self.Show()
def OnRun(self, event):
t=threading.Thread(target=self.__run)
t.start()
def OnClose(self, event):
self.Destroy()
#external function call
def __run(self):
externFunc()
if __name__ == '__main__':
app = wx.App(False)
progFrame(None)
app.MainLoop()
External function code:
import sys
def externFunc():
print "Starting execution..."
#a bunch of code...
#this is the code for the Yes/No prompt and what introduced the buggy behavior
if(os.path.isdir(mirror_source_path) or os.path.isdir(mirror_dest_path)):
app = wx.App(False)
dlg = wx.MessageDialog(None, "Something bad happened. Continue?","Warning",wx.YES_NO | wx.ICON_QUESTION)
retCode = dlg.ShowModal()
if (retCode == wx.ID_YES):
print "Continuing."
else:
print "Aborted."
return None
sys.exit(0)
dlg.Destroy()
#more function code...
print "Success!"
You cannot have 2 wxPython main loops running at the same time. That will cause some pretty screwy behavior. Personally I think I would split this code into 2 threads. When the first one finishes, it sends a message using wx.CallAfter and pubsub or wx.PostEvent and at that time you can do your if statement in a wxPython handler.
If you continue, then you spin up a second thread with the rest of the function code.

Thread for UI design with wxPython

I'm currently designing a software UI based.
I have created a first python script used as my main source code.
Below is my script
import OneTouchToolLogs
import UserInterface
import wx
import XML_Parse
import threading
if __name__ == '__main__':
#Init the logging - Tools and also logcat scenario
Logs = OneTouchToolLogs.Logging()
LogFileName = Logs.LogFileOpen()
Logs.LogMessage(LogFileName, "LOG" , "Starting OneTouchAutomationTools")
#Initialized User Interface
Logs.LogMessage(LogFileName, "LOG" , "Loading user interface")
app = wx.App()
frame = UserInterface.UserInterface(None, -1, 'OneTouchAutomation')
app.MainLoop()
I have created another python file which contain the class UserInterface to make it clearer.
The new python class is done as below:
import wx
class UserInterface(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent,id, title)
self.parent = parent
self.worker = None
self.initialize()
def initialize(self):
menubar =wx.MenuBar()
#CREATE FILE MENU SECTION
fileMenu = wx.Menu()
fileMenu.Append(wx.ID_NEW, '&New suites\tCTRL+N')
fileMenu.Append(wx.ID_OPEN, '&Open suites\tCTRL+O')
With the design, I have done, the UI become a blocking point for the overall execution as it's not done on a thread.
I have modified my main script to replace
app.MainLoop()
by
t = threading.Thread(target=app.MainLoop)
t.setDaemon(1)
t.start()
The result is that the thread is well created but he is killed in a second. I just see the window and it's close.
Any one know I to be able to create this interface using my UserInterface class and start it in a thread to allow the main program to continu ?
In most cases, you will want the wxPython script to be the main application (i.e. the main thread). If you have a long running task, such as parsing a file, downloading a file, etc, then you will put that into a separate thread that your wxPython program will create. To communicate from the spawned thread back to the wxPython program, you will need to use a thread-safe method, such as wx.CallAfter or wx.PostEvent.
I recommend checking out the wxPython wiki for additional information:
http://wiki.wxpython.org/LongRunningTasks

Is there some sort of printing buffer that overflows, when WriteText (AppendText, SetLabel, etc.) continuously in wxPython?

I have some text that goes somewhat continuously on the output (somewhat = every two seconds approx in the real application).
When just print into the terminal, everything is ok, no matter how long I keep the running loop.
However, when directing the print to a wx frame, the print action itself goes well, no problem here, but if I keep the running loop more than 10-20 cycles, I am no longer able to close the window normally (mouse or Ctrl+F4 in this example, but the same if I construct an explicit Exit event menu). The action in thread stops, but the application hangs -- it can only be closed by the OS (Windows in my case) with a "force close / wait for the program to respond" dialog. Though, it can be closed immediately with Ctrl+C when the terminal window is on focus. It looks like something fills up with too much text and locks the closing process.
Happens with either WriteText or AppendText on a multiline wxTextCtrl window, or with the SetLabel as in the test code below.
In this test, if I close the wx window almost immediately after launch, it closes well. If I keep running the loop longer (or by comment out the two time.sleep() lines), then the hang will succeed whenever trying to close the window.
What am I missing here ? (Is there something I have to flush ?)
edit Hmm, the issue could be related to "threadsafe method", as described briefly here. Will do some tests in that direction, have to find out the proper usage of each of the wx.CallAfter, wx.CallLater or wx.PostEvent.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
import time
import threading
class TestFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.sometext = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_READONLY)
self.thread = None
self.alive = threading.Event()
self.__set_properties()
self.__do_layout()
self.__attach_events()
self.StartThread()
def __set_properties(self):
self.SetTitle("test")
def __do_layout(self):
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.sometext, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 0)
self.SetSizer(sizer)
sizer.Fit(self)
self.Layout()
def __attach_events(self):
self.Bind(wx.EVT_CLOSE, self.OnClose)
def OnClose(self, event):
""" stop & close on system window close """
self.StopThread()
self.Destroy()
def StartThread(self):
""" start the thread """
self.thread = threading.Thread(target=self.TestThread)
self.thread.setDaemon(1)
self.alive.set()
self.thread.start()
def StopThread(self):
""" stop the thread, wait util it is finished """
if self.thread is not None:
self.alive.clear()
self.thread.join()
self.thread = None
def TestThread(self):
""" main thread """
while self.alive.isSet():
self.sometext.SetLabel("Hello")
time.sleep(0.5)
self.sometext.SetLabel("Python")
time.sleep(0.5)
class MyApp(wx.App):
def OnInit(self):
wx.InitAllImageHandlers()
frame = TestFrame(None, -1, "")
self.SetTopWindow(frame)
frame.Show(1)
return 1
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
I've changed every SetLabal with AppendtText. I'm don't know why, but SetLabel function don't work correctly for me.
After a few seconds the application will stop working. This is my error output:
(python:14099): Pango-CRITICAL **: pango_layout_get_iter: assertion 'PANGO_IS_LAYOUT (layout)' failed
Segmentation fault
After that I used CallAfter function http://wiki.wxpython.org/CallAfter the application started correctly working for me. My version of TestThread:
def TestThread(self):
""" main thread """
while self.alive.isSet():
wx.CallAfter(self.sometext.AppendText, "Hello")
time.sleep(0.5)
wx.CallAfter(self.sometext.AppendText, "Python")
time.sleep(0.5)
wx.CallAfter(self.sometext.Clear)
I hope this help you.

Python Tkinter: App hangs when changing window title via callback event

I'm creating a simple Python UI via Tkinter, and I'd like use self.title to have the window title change when a callback event is generated.
If I bind the event to a button, or call the event handler directly within the Tk thread, the window title changes as expected. However, I intend this event to be invoked by a separate thread, and I've found that using title in the callback event handler causes the app to hang.
Other tasks that I have in the event handler (such as updating a label) work just fine, so I have to assume that the event is being invoked properly. I've tried wm_title instead of title, but didn't see a difference. I've dug around and found nothing odd about title's usage, just call it with a string to set the title.
Here's a stripped-down sample that replicates the problem (I'm running v2.7.1 on WinXP FYI); the app runs fine for 10 seconds (can move the window, resize, etc.), after which Timer generates the event and the app then freezes.
import Tkinter
import threading
class Gui(Tkinter.Tk):
def __init__(self, parent=None):
Tkinter.Tk.__init__(self, parent)
self.title('Original Title')
self.label = Tkinter.Label(self, text='Just a Label.',
width=30, anchor='center')
self.label.grid()
self.bind('<<change_title>>', self.change_title)
timer = threading.Timer(10, self.event_generate, ['<<change_title>>'])
timer.start()
def change_title(self, event=None):
self.title('New Title')
G = Gui(None)
G.mainloop()
I encountered the same problem, where the UI hangs when calling self.title() from a thread other than the main thread. Tkinter expects all UI stuff to be done in the same thread (the main thread).
My solution was to have the separate thread put functions in a queue. The queue is serviced periodically by the main thread, making use of the after(ms) function provided by Tkinter. Here's an example with your code:
import Tkinter
import threading
from Queue import Queue, Empty
class Gui(Tkinter.Tk):
def __init__(self, parent=None):
Tkinter.Tk.__init__(self, parent)
self.ui_queue = Queue()
self._handle_ui_request()
self.title('Original Title')
self.label = Tkinter.Label(self, text='Just a Label.',
width=30, anchor='center')
self.label.grid()
self.bind('<<change_title>>', self.change_title)
timer = threading.Timer(1, self.event_generate, ['<<change_title>>'])
timer.start()
def change_title(self, event=None):
# Separate the function name, it's args and keyword args,
# and put it in the queue as a tuple.
ui_function = (self.title, ('New Title',), {})
self.ui_queue.put(ui_function)
def _handle_ui_request(self):
'''
Periodically services the UI queue to handles UI requests in the main thread.
'''
try:
while True:
f, a, k = self.ui_queue.get_nowait()
f(*a, **k)
except Empty:
pass
self.after(200, self._handle_ui_request)
G = Gui(None)
G.mainloop()
Well, your code actually runs fine to me.
Except that, when interrupted before the ten secs, it says "RuntimeError: main thread is not in main loop"
I'm using python 2.6.6 under ubuntu 10.10
Hope this was of some help.
I tried it as well with 2.6.2 (Windows) and the caption/title didn't change. No runtime error though.

Categories

Resources