Using multiple threads with wxPython - python

I am writing a wxpython program.
If you open a file in the program, it counts the number of lines in the file and displays it in a staticText Widget.
Here's the relevant code:
In class Frame
def on_select_file(self, event):
'''Event handler for selecting file'''
filepath = getFile() # This is a seperate method which asks the user to select a file and returns the path
threading.Thread(target = methods.count_lines, args = (filepath, self.staticText)).start()
methods.py
def count_lines(filepath, staticText):
with open(filepath) as f:
for i, _ in enumerate(f): pass
staticText.SetLabel(str(i+1))
Most of the files I'm dealing with are very large (3-4 GB) with around 25 million lines. So if I open a large file, it takes a few tens of seconds to count and display the no. of lines. However if I select a large file and before the staticText widget is updated, open another file which is smaller, the staticText widget shows the new count, but after some time shows the count of the previous file. I understand this is because the previous thread was still running and updated the widget after it ended.
I tried working around it by passing a flag variable as a parameter to the counter function, to check if the widget has been updated. However it does not seem to work. Is there any other way to avoid this?

Essentially, create a custom event type that contains the current line count as a member, and in your worker thread periodically post an event of that type to the class that contains your static text widget using wx.PostEvent(). Then, when the main thread resumes and processes its event loop, you can use the line count reported by the received event(s) to set the text string.
Something like this should work:
import time
from threading import *
import wx
import os.path
EVT_LINE_NUMBER_UPDATE_ID = wx.NewId()
class LineNumberUpdateEvent(wx.PyEvent):
def __init__(self, lineNum):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_LINE_NUMBER_UPDATE_ID)
self.lineNumber = lineNum
class WorkerThread(Thread):
def __init__(self, notify_window, filename):
Thread.__init__(self)
self._notify_window = notify_window
self._filename = filename
self.start()
def run(self):
with open(this._filename,"r") as file:
count = 0;
for line in file:
count++
if count % 50 == 0: # post an event every 50 lines
wx.PostEvent(self._notify_window, LineNumberUpdateEvent(count))
wx.PostEvent(self._notify_window, LineNumberUpdateEvent(count)) # last event
class MainFrame(wx.Frame):
def __init__(self, parent, id, filename):
wx.Frame.__init__(self, parent, id, 'Threaded File Loader')
self.status = wx.StaticText(self, -1, '', pos=(0,100))
self.Bind(EVT_LINE_NUMBER_UPDATE_ID, self.OnLineNumberUpdate)
if (os.path.isfile(filename))
self.worker = WorkerThread(self,filename)
def OnLineNumberUpdate(self, event):
self.status.SetLabel(str(event.lineNumber))
This was adapted from an example posted on the wx Wiki:
http://wiki.wxpython.org/LongRunningTasks

I modified the code in the Sir Digby Chicken Caesa's answer to suit my need. Rather than creating a custom event and having it fired after some time interval, I set up the line counting thread such that it could be aborted every time a file open event occurs and a previous instance of this thread is still running.
Here's a short example of the implementation:
import threading.Thread
class Line_Counter(threading.Thread):
def __init__(self, staticText, filename):
threading.Thread.__init__(self)
self.staticText = staticText
def run(self):
self.exit = False
with open(self.filename) as f:
if self.exit:
return
for count, _ in enumerate(f): pass
self.staticText.SetLabel(str(count + 1))
def abort(self):
self.exit = True
class MainFrame(wx.Frame):
def __init__(self, parent, id, filename):
wx.Frame.__init__(self, parent, id, 'Threaded File Loader')
self.line_counter = False # Set up a dummy thread variable
#### remaining code
self.file_open_button.Bind( wx.EVT_BUTTON, self.OnOpenFile ) # Event handler for opening file
def OnOpenFile(self, event):
filepath = getFile() # Creates a file open dialog and returns the file path
if self.line_counter: # Checks if a counter thread is already running
self.line_counter.abort() # Stops the thread if True
self.line_counter = Line_Counter(self.staticText, filename).start() # Starts a new thread

Related

How do I appropriately use QWaitCondition and QMutex?

I have two classes, a consumer and a producer. To prevent a race condition between threads, I've tried using QWaitCondition and QMutex, but I keep running into timing issues. The producer object fills my home object's buffer and a buffer of its own. The consumer object copies this buffer and displays the data in the buffer. As it is displaying, I would like the producer object to fill the buffer again and then wait until the displaying is complete. Immediately, the consumer object would copy the buffer and continue the display. Rinse and repeat. However, I can not figure out the timing sequence with my code.
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import QWaitCondition, QMutex
mutex = QMutex()
displayFramesDone = QWaitCondition()
copyFramesDone = QWaitCondition()
sequenceDone = QWaitCondition()
class Producer(QThread):
finished = pyqtSignal()
def __init__(self, home_obj):
self.parent = self
self.home = home_obj
self.prod_buffer = []
"""
functions
"""
def run(self):
self.runSequence()
sequenceDone.wakeAll()
while self.home.condition == True:
self.runSequence()
mutex.lock()
displayFramesDone.wait(mutex)
mutex.unlock()
sequenceDone.wakeAll()
mutex.lock()
copyFramesDone.wait(mutex)
mutex.unlock()
self.finished.emit()
class Consumer(QThread):
finished = pyqtSignal()
def __init__(self, home_obj):
self.parent = self
self.home = home_obj
self.con_buffer = []
"""
functions
"""
def run(self):
while self.home.condition == True:
mutex.lock()
sequenceDone.wait(mutex)
mutex.unlock()
self.copyFrames()
copyFramesDone.wakeAll()
self.displayFrames()
displayFramesDone.wakeAll()
self.finished.emit()
class Home(QWidget):
def __init__(self):
super().__init__()
self.condition == True
self.buffer = []
I've never used QWaitCondition or QMutex before, so if I'm using them appropriately is also another matter, however, this seems to work only occasionally when the timing manages to align with the required sequence.
Additionally, I'd like to know if anyone could answer the following:
Must a wake signal be inside of a mutex block?
Is QWaitCondition/QMutex a good tool for this type of timing problem?

Why is the main thread of my multithreaded application unresponsive to Ctrl+C?

I have written a multithreaded application to watch and respond to changes in given list of files. I have a Watch class which gets the file size and sets it to size variable upon first call. Then, after a few seconds it again gets the size of the file and compares it with with the previous size and, if changed, sets size to the current size of the file. Furthermore there is a WatchWorker class which is a subclass of threading.Thread. The WatchWorker which makes use of Watch class to 'watch ' a given file.
Now here is the real problem :
The code I have written is working and notifies the user when a change is detected. But there is no response when I try to quit from the application using Ctrl+C. I'm on Windows.
Code:
import time
import threading
import os
class Watch(object):
def __init__(self, path, time=5):
self.path = path
self.time = time
self.size = os.stat(path).st_size
def loop(self):
while True:
time.sleep(self.time)
size = os.stat(self.path).st_size
if size != self.size:
self.size = size
print "Change detected in file {}".format(self.path)
class Watch_Worker(threading.Thread):
def __init__(self, path, *args, **kwargs):
super(Watch_Worker, self).__init__(*args, **kwargs)
self.path = path
def run(self):
super(Watch_Worker, self).run()
Watch(self.path).loop()
def main(*args):
for i in args:
thrd = Watch_Worker(path=i)
thrd.start()
print 'Watching ' + i
print "From main thread"
if __name__ == '__main__':
main('blah.js', 'cs.c', 'ab.rb')
Edit
Modified code to compare the values produced os.stat('somefile.ext').st_size).
I solved the problem of the main thread being prevented from exiting by setting self.daemon = True. The main thread does not wait for the threads to exit. This is solved by adding a infinite while loop withtime.sleep(5) to the end of main function. Here is the code:
class Watch_Worker(threading.Thread):
def __init__(self, path, time=2, *args, **kwargs):
super(Watch_Worker, self).__init__(*args, **kwargs)
self.path = path
self.time = time
self.size = os.stat(path).st_size
self.daemon = True
def run(self):
super(Watch_Worker, self).run()
while True:
time.sleep(self.time)
size = os.stat(self.path).st_size
if size != self.size:
self.size = size
print "Change detected in file {}".format(self.path)
def main(*args):
for i in args:
thrd = Watch_Worker(path=i)
thrd.start()
print 'Watching ' + i
while True:
time.sleep(5)
if __name__ == '__main__':
main('blah.js', 'cs.c', 'ab.rb')

Thread synchronization in GTK3+ and Python

I'm currently learning both Python and GTK 3+ and I've a problem when synchronizing threads. I'll try to be quick and clear:
I have to make a Social Network client. As the purpose is to learn how to create a GUI the "access to the social network API" will be simulated, but I have to "wait" for network responses with time.sleep(). Calling time.sleep() in the main thread freezes the GUI (it stops the execution of Gtk.Main()) so I have to make all my connections in a separate thread.
And here lies my problem. When I'm authenticating a user (verifying_credentials) I need to wait from that thread to finish to continue the execution of the main program. If I try a Thread.join GUI freezes. I've tried using queues, but queue.get is also blocking Gtk.main().
I've tried emitting a signal when my thread is finished , but the handler starts in the same thread, so when I try to modify the GUI (which I need) , program crashes (you're not supposed to touch the GUI from anywhere except main thread).
My solution ? I do busy-waiting / active-waiting , which is by definition an antipattern. I keep asking if the thread has finished and forcing cicles of Gtk.main()
There has to be another way, a more elegant / efficient way than mine.
I don't know if I can signal another thread, or there is a way of using queues without blocking the main thread. Any help will be very much appreciated.
The python code :
from os.path import abspath, dirname, join
import gettext
import threading
import math
import time
import random
import locale
from gi.repository import Gtk, Gdk, GLib, GObject
import list
APP = "UDC_Social_API"
user_list = {
"user": "password",
"admin": "admin",
"carnotan": "1234"
}
class UDC_Social_API:
def on_applicationwindow1_key_press_event(self, widget, event):
# keyname = Gdk.keyval_name(event.keyval)
# print (_("Key %(name)s (%(val)d) was pressed" )%{"name":keyname, "val":event.keyval})
if event.keyval == 65293:
self.on_login_button_clicked()
def delay(self):
if self.delay_option.get_active():
time.sleep(math.exp(random.random()*5))
else:
pass
def active_waiting(self):
while self.finished is False:
Gtk.main_iteration_do(False)
self.finished = False
self.z_handler(None)
def verify_credentials(self, user, password):
GLib.idle_add(self.active_waiting)
self.delay()
if user in user_list:
if password == user_list.get(user):
self.authentication = True
self.finished = True
else:
self.authentication = False
self.finished = True
else:
self.authentication = False
self.finished = True
def on_login_button_clicked(self, data=None):
user = self.user_entry.get_text()
password = self.password_entry.get_text()
thread = threading.Thread(target=self.verify_credentials, args=(user, password))
thread.daemon = True
thread.start()
def z_handler(self, data=None):
if self.authentication is False:
self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
self.message_dialog.run()
self.message_dialog.hide()
return False
else:
self.window.hide()
print ("Success!")
def on_applicationwindow1_destroy(self, data=None):
Gtk.main_quit()
def on_gtk_about_activate(self, menuitem, data=None):
self.aboutdialog.run()
self.aboutdialog.hide()
def on_gtk_cut_activate(self, widget):
# Get the bounds of the selected text
bounds = self.focus.get_selection_bounds()
# if the bounds of the selection are not an empty tuple,
# put the selection in the variable chars
# and copy it to the clipboard
# (get_selection_bounds returns an empty tuple if there is no selection)
# then delete the selection
if bounds:
chars = self.focus.get_chars(*bounds)
self.clipboard.set_text(chars, -1)
self.focus.delete_text(bounds[0], bounds[1])
else:
pass
def on_gtk_copy_activate(self, widget):
# Get the bounds of the selected text
bounds = self.focus.get_selection_bounds()
# if the bounds of the selection are not an empty tuple,
# put the selection in the variable chars
# and copy it to the clipboard
# (get_selection_bounds returns an empty tuple if there is no selection)
if bounds:
chars = self.focus.get_chars(*bounds)
self.clipboard.set_text(chars, -1)
else:
pass
def on_gtk_paste_activate(self, widget):
# Get the text from the clipboard
text = self.clipboard.wait_for_text()
if text is not None:
# If there's text selected in the target
# delete it and paste the contents of the clipboard
bounds = self.focus.get_selection_bounds()
if bounds:
self.focus.delete_text(bounds[0], bounds[1])
self.focus.insert_text(text, bounds[0])
# else insert the text in the current position of the cursor in the target
else:
pos = self.focus.get_position()
self.focus.insert_text(text, pos)
else:
pass
def on_entry_focus(self, widget, event):
self.focus = widget
def create_menubar(self):
self.file_menu=self.builder.get_object("menuitem1")
self.edit_menu=self.builder.get_object("menuitem2")
self.options_menu=self.builder.get_object("option")
self.help_menu=self.builder.get_object("menuitem4")
self.languages_menu=self.builder.get_object("menuitem3")
self.delay_option = self.builder.get_object("delay_option")
self.gtk_quit_menu=self.builder.get_object("gtk_quit_menu")
self.gtk_cut_menu=self.builder.get_object("gtk_cut_menu")
self.gtk_copy_menu=self.builder.get_object("gtk_copy_menu")
self.gtk_paste_menu=self.builder.get_object("gtk_paste_menu")
self.gtk_about_menu=self.builder.get_object("gtk_about_menu")
self.galician_option=self.builder.get_object("radiomenuitem1")
self.spanish_option=self.builder.get_object("radiomenuitem2")
self.english_option=self.builder.get_object("radiomenuitem3")
def set_menubar_names(self):
self.file_menu.set_label(_("_File"))
self.edit_menu.set_label(_("_Edit"))
self.options_menu.set_label(_("_Options"))
self.help_menu.set_label(_("_Help"))
self.languages_menu.set_label(_("_Languages"))
self.delay_option.set_label(_("_Delay"))
self.gtk_quit_menu.set_label(_("Quit"))
self.gtk_copy_menu.set_label(_("Copy"))
self.gtk_cut_menu.set_label(_("Cut"))
self.gtk_paste_menu.set_label(_("Paste"))
self.gtk_about_menu.set_label(_("About"))
self.galician_option.set_label(_("_Galician"))
self.spanish_option.set_label(_("_Spanish"))
self.english_option.set_label(_("_English"))
def create_login_box(self):
self.user_entry = self.builder.get_object("user_entry")
self.password_entry = self.builder.get_object("password_entry")
self.user_label=self.builder.get_object("user_label")
self.password_label=self.builder.get_object("password_label")
self.login_button=self.builder.get_object("login_button")
def set_login_box_names(self):
self.user_entry.set_placeholder_text(_("user"))
self.password_entry.set_placeholder_text(_("password"))
self.user_label.set_label(_("User"))
self.password_label.set_label(_("Password"))
self.login_button.set_label(_("Login"))
def create_about_dialog(self):
self.aboutdialog = self.builder.get_object("aboutdialog1")
self.aboutdialog.set_transient_for(self.window)
def set_about_dialog(self):
self.aboutdialog.set_comments(_("Developed for GTK 3+ and Python 3.4"))
def reset_names(self):
self.set_menubar_names()
self.set_login_box_names()
def on_radiomenuitem1_toggled(self, widget):
if widget.get_active():
self.lang_gl_ES.install()
self.reset_names()
self.window.queue_draw()
else:
pass
def on_radiomenuitem2_toggled(self, widget):
if widget.get_active():
self.lang_es_ES.install()
self.reset_names()
self.window.queue_draw()
else:
pass
def on_radiomenuitem3_toggled(self,widget):
if widget.get_active():
self.lang_en_US.install()
self.set_menubar_names()
self.window.queue_draw()
else:
pass
def set_languages(self):
WHERE_AM_I = abspath(dirname(__file__))
locale.setlocale(locale.LC_ALL, '')
locale.bindtextdomain(APP, WHERE_AM_I)
locale_path = WHERE_AM_I +'/'
self.builder.set_translation_domain(APP)
gettext.find(APP,localedir=locale_path,languages=['gl_ES'])
gettext.find(APP,localedir=locale_path,languages=['es_ES'])
gettext.find(APP,localedir=locale_path,languages=['en_US'])
gettext.install(APP,locale_path)
gettext.textdomain(APP)
gettext.bindtextdomain(APP,locale_path)
self.lang_gl_ES=gettext.translation(APP,localedir=locale_path, languages=['gl_ES'])
self.lang_es_ES=gettext.translation(APP,localedir=locale_path, languages=['es_ES'])
self.lang_en_US=gettext.translation(APP,localedir=locale_path, languages=['en_US'])
def set_signals(self):
handlers = {
"on_applicationwindow1_destroy": self.on_applicationwindow1_destroy,
"on_gtk_about_activate": self.on_gtk_about_activate,
"on_login_button_clicked": self.on_login_button_clicked,
"on_applicationwindow1_key_press_event": self.on_applicationwindow1_key_press_event,
"on_entry_focus": self.on_entry_focus,
"on_gtk_cut_activate": self.on_gtk_cut_activate,
"on_gtk_copy_activate": self.on_gtk_copy_activate,
"on_gtk_paste_activate": self.on_gtk_paste_activate,
"on_radiomenuitem1_toggled": self.on_radiomenuitem1_toggled,
"on_radiomenuitem2_toggled": self.on_radiomenuitem2_toggled,
"on_radiomenuitem3_toggled": self.on_radiomenuitem3_toggled
}
self.builder.connect_signals(handlers)
def __init__(self):
# GObject.signal_new("z_signal", Gtk.ApplicationWindow, GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ())
self.builder = Gtk.Builder()
self.builder.add_from_file("p1.glade")
self.window = self.builder.get_object("applicationwindow1")
self.set_languages()
self.create_menubar()
self.create_login_box()
self.create_about_dialog()
self.reset_names()
self.set_signals()
self.focus = None
self.finished = False
self.authentication = False
# self.statusbar = self.builder.get_object("statusbar1")
# self.context_id = self.statusbar.get_context_id("status")
# self.status_count = 0
self.message_dialog = self.builder.get_object("messagedialog1")
self.message_dialog.set_transient_for(self.window)
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.window.show_all()
if __name__ == "__main__":
GObject.threads_init()
main = UDC_Social_API()
Gtk.main()
Glade file is in pastebin, because it will exceed post size limit.
http://pastebin.com/8S3k7f6J
Thanks in advance for any help you could provide.
You can use GLib.idle_add to schedule a callback to be executed by the event loop in the main thread of your program. This means it provides a safe way to schedule a GUI update from a background thread. So, you can just let your background thread run normally, let the main thread return control to the event loop, and then make the appropriate GUI updates from the background thread via GLib.idle_add once it's done:
def verify_credentials(self, user, password):
self.delay()
if user in user_list:
if password == user_list.get(user):
self.authentication = True
else:
self.authentication = False
else:
self.authentication = False
# Schedule z_handler to be called by the event loop in the main thread.
GLib.idle_add(z_handler, None)
def z_handler(self, data=None):
if not self.authentication:
self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
self.message_dialog.run()
self.message_dialog.hide()
return False
else:
self.window.hide()
print ("Success!")
You're actually pretty close to using this same method, you're just doing it in an awkward way - you're scheduling active_waiting to run in the main thread, which waits until the background thread is done, and then calls z_handler. Scheduling z_handler directly, after the background thread is done with its work, is much simpler.

PyQt Qthread automatic restart

I'm trying to understand how thread works, and i'm stuck with this problem. That's my program explained:
i made a simple GUI in pyqt that use a QObject as a worker class. When i press the botton start the gui read a random value from a list and pass it to the thread, that print the
next five number. When the thread finish the work, it pass the data to the gui. Now i want the GUI to restart automatically a new thread with a new start value. I can restart the thread by pressing start again, but i need to start it without human interaction. Are there
any method?
thanks in advance
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
import sys
import numpy as np
class SomeObject(QObject):
finished = pyqtSignal(object)
valore = pyqtSignal(object)
vector = pyqtSignal(object)
def __init():
super(SomeObject, self).__init__()
def longRunning(self):
vec = []
end = self.count + 5
while self.count < end:
time.sleep(1)
vec.append(self.count)
self.valore.emit(self.count)
self.count += 1
self.finished.emit(vec)
#self.vector.emit()
def setCount(self, num):
self.count = num
class GUI(QDialog):
def __init__(self, parent = None):
super(GUI, self).__init__(parent)
#declare QThread object
self.objThread = QThread()
#declare SomeObject type, and move it to thread
self.obj = SomeObject()
self.obj.moveToThread(self.objThread)
#connect finished signal to nextVector method
self.obj.finished.connect(self.nextVector)
#connect valore to self.prova method
self.obj.valore.connect(self.prova)
#self.obj.vector.connect(self.nextVector)
#Connect thread.start to the method long running
self.objThread.started.connect(self.obj.longRunning)
botton = QPushButton("start")
self.connect(botton, SIGNAL("clicked()"), self.showcount)
box = QHBoxLayout()
box.addWidget(botton)
self.setLayout(box)
#a list of random number
a = np.random.randint(10, size = 5)
self.iter = iter(a)
def showcount(self):
"""
When botton clicked, read the next value from iter, pass it to
setCount and when start the thread
"""
try:
a = self.iter.next()
print a
self.obj.setCount(a)
self.objThread.start()
except StopIteration:
print "finito"
#self.obj.setCount(a)
#self.objThread.start()
#print self.objThread.currentThreadId()
def prova(self, value):
"""
Connected to signal valore, print the value
"""
print value
def nextVector(self, vec):
"""
Print the whole vector
"""
print vec
self.objThread.quit()
try:
a = self.iter.next()
print a
self.obj.setCount(a)
self.objThread.start()
except StopIteration:
print "finito"
app = QApplication(sys.argv)
form = GUI()
form.show()
app.exec_()
You already have it set up. When your thread is finished it emits the finished signal which calls the nextVector method, so just call the start method at the end of nextVector.
def nextVector(self, vec):
...
self.showcount()
# end nextVector
You may also want to change to the new signal connection for your QPushButton
button.clicked.connect(self.showcount)

How do I handle multiple EVT_TEXT events in wxPython?

This is one part of a two part question (other part is here)
So here's what I'm looking for: A function which is bound to the EVT_TEXT event of a text control that waits a few seconds, then calls another function at the end of the delay time. Thats easy enough, but, I'd like it to reset the delay time every time a new EVT_TEXT event is generated. The effect I'm looking for is to have a user type into the text control, then after I assume they're done, I run the function described in the other part of this question which spell checks what they've written.
So the simple approach I tried was this:
def OnEdit(self, event):
for i in range(0,3):
print i
time.sleep(1)
However, this just forces a 3 second wait, no matter what. How do I "break in" to this function to reset the counter? Thanks in advance.
EDIT: Turns out the way to do this was with threading. Yippee
The full threading answer, built with the help of this tutorial:
from threading import *
import wx
import time
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
win.Connect(-1, -1, EVT_RESULT_ID, func)
class MyGui(wx.Frame):
def __init__(self):
self.spellchkthrd = None
#lots of stuff
self.input = wx.TextCtrl(self.panel, -1, "", size=(200, 150), style=wx.TE_MULTILINE|wx.TE_LEFT|wx.TE_RICH)
self.Bind(wx.EVT_TEXT, self.OnEdit, self.input)
EVT_RESULT(self, self.OnSplCheck)
def OnEdit(self, event):
if not self.spellchkthrd:
self.spellchkthrd = SpellCheckThread(self)
else:
self.spellchkthrd.newSig()
def OnSplCheck(self, event):
self.spellchkthrd = None
#All the spell checking stuff
class ResultEvent(wx.PyEvent):
def __init__(self):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
class SpellCheckThread(Thread):
def __init__(self, panel):
Thread.__init__(self)
self.count = 0
self.panel = panel
self.start()
def run(self):
while self.count < 1.0:
print self.count
time.sleep(0.1)
self.count += 0.1
wx.PostEvent(self.panel, ResultEvent())
def newSig(self):
print "new"
self.count = 0

Categories

Resources