Need advice to keep GUI responsive - python

Basically, what I have is a GUI with some QLineEdits, a "search button" and a table. You hit the button and a class called DataGrabber searches a database for data, processes them, returns a list with dictionaries with which the table is filled, accordingly. These searches can take a while and I need to keep my GUI responsive. Also, I want the statusbar message to change as long as the search is going on (something like "Searching." -> "Searching.." -> "Searching...", the functionality is not rly important here, it's just about understanding how I can handle this properly).
I started with threading everything and created a queue between the thread that handles the search and the function that handles the statusbar, to know when the search is done. But that seems really goofy. Especially since Qt provides all kind of tools, like QThread and Signals. But I'm rly lost right now. What would be the best way to handle the responsiveness when having such a time-consuming action like a database search? And what would be the best way to tell the main/child thread that the search is finished?
Here is a reduced version of what I have right now:
class GUI(Ui_MainWindow, InitGlobals):
def __init__(dialog):
...
self.start_button_3.clicked.connect(\
lambda: self.start_search(self.result_tab_3))
...
def start_search():
...
search_paras = [3,
self.name_box_3.text(),
self.project_combo_3.currentText(),
self.voltage_box.text(),
self.volume_box.text()]
queue = Queue()
thr = Thread(target=self.search_thread, args=(queue, search_paras,))
thr.start()
data_lst = statusbar_load(queue, self.statusbar, option="loader")
thr.join()
self.statusbar.showMessage("Search completed...")
for dic in data_lst:
self.write_to_table(dic, tab)
def search_thread(self, queue, args):
grabber = DataGrabber(self.db_config)
...
if args[0] == 3:
queue.put(grabber.alpha_search(args[1], args[2],
args[3], args[4]))
queue.task_done()
def statusbar_load(queue, statusbar_obj, option="spinner"):
data = None
i = 0
while data is None:
try:
data = queue.get(timeout=0.1)
except Empty:
if option == "spinner":
status = ["-", "\\", "|", "/"]
statusbar_obj.showMessage("Searching [" + status[i%4] + "]")
....
i = i + 1
return data

This can be handled with signals. You can use signals to send the results to the GUI and to update the GUI of the progress.
Here is a quick example of the implementation with a progress bar and status label. These can be included in a status bar if you would like:
class GUITest(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout()
self.button = QtWidgets.QPushButton('Run')
self.button.clicked.connect(self.run)
self.result_box = QtWidgets.QTextBrowser()
self.label = QtWidgets.QLabel()
self.progress_bar = QtWidgets.QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(0)
layout.addWidget(self.button)
layout.addWidget(self.result_box)
layout.addWidget(self.label)
layout.addWidget(self.progress_bar)
self.setLayout(layout)
def run(self):
self.progress_bar.setVisible(True)
self.label.setText('Searching...')
self.thread = QtCore.QThread()
self.data_grabber = DataGrabber()
self.data_grabber.moveToThread(self.thread)
self.data_grabber.update_progress.connect(self.update_progress_bar)
self.data_grabber.results.connect(self.display_results)
self.data_grabber.finished.connect(self.complete)
self.data_grabber.finished.connect(self.thread.quit)
self.data_grabber.finished.connect(self.data_grabber.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.started.connect(self.data_grabber.run)
self.thread.start()
def update_progress_bar(self):
self.progress_bar.setValue(self.progress_bar.value() + 1)
def complete(self):
self.label.setText('Complete')
self.progress_bar.setVisible(False)
def display_results(self, results):
for key, value in results.items():
self.result_box.append('%s: %s' % (key, value))
class DataGrabber(QtCore.QObject):
finished = QtCore.pyqtSignal()
update_progress = QtCore.pyqtSignal()
results = QtCore.pyqtSignal(dict) # set the type of object you are sending
def __init__(self):
super().__init__()
self.count = 0
def run(self):
# search database here and emit update_progress when appropriate
while self.count <= 100:
self.update_progress.emit()
self.count += 1
time.sleep(0.02)
self.send_results() # when done, send the results
self.finished.emit()
def send_results(self):
results = {'one': 'Result One', 'two': 'Result Two', 'three': 'Result Three'}
self.results.emit(results)

Related

How to pause a thread (python)

The context:
I'm building a Graphical Interface with Qt creator and the "behaviour" file in python. A test version of my GUI is:
The expected behaviour:
I am running 2 different threads which are referred to the same function with different input arguments. With the SELECTOR button I can assign the value of 1 or 2 to a variable (and display it)
The button Start thread enables the correct thread to start (the first time).
The loop should be turned off by the stop button by modifying the global running variable.
This is my code
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui, uic
import sys
import threading
import time
import Queue
running = False
first_thread = None
second_thread = None
form_class = uic.loadUiType("simple2.ui")[0]
q = Queue.Queue()
select = 0
def action(string, queue): #function called by threads
global running
while(running):
phrase = string
if queue.qsize() < 10:
queue.put(phrase)
#else:
# print queue.qsize()
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
#buttons
self.startButton.clicked.connect(self.start_clicked)
self.stopButton.clicked.connect(self.stop_clicked)
self.selector.clicked.connect(self.sel_click)
#variables
self.first = False
self.second = False
#queue
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.update_phrase)
self.timer.start(1)
def start_clicked(self): #start button callback
global select
if select > 0:
global running
running = True
print "started"
if (not self.first) & (select == 1):
first_thread.start()
self.first = True
if (not self.second) & (select == 2):
second_thread.start()
self.second = True
self.startButton.setEnabled(False)
self.startButton.setText('Starting...')
def stop_clicked(self): #stop button callback
global running
running = False
print "stopped"
self.startButton.setEnabled(True)
self.startButton.setText('Start Thread')
def sel_click(self): #selector button callback
global select
if select < 2:
select = select + 1
else:
select = 1
self.thread_counter.setText(str(select))
def update_phrase(self): #looping function
global running
if (not q.empty()) & running:
self.startButton.setText('Thread on')
abc = q.get()
print abc
def closeEvent(self, event):
global running
running = False
if __name__ == "__main__":
first_thread = threading.Thread(target=action, args = ("first", q))
second_thread = threading.Thread(target=action, args = ("second", q))
app = QtGui.QApplication(sys.argv)
w = MyWindowClass(None)
w.setWindowTitle('Multiple threads test in python')
w.show()
app.exec_()
For now, each thread should simple print on terminal their arguments ("First" or "Second").
If threads are started for the first time, my code works. But I would like to switch between threads infinite times.
Since threads cannot be stopped, is there a way to "pause" them?
I cannot find a solution, I hope someone will help me also with a piece of code. Thank you in advance
You can use Lock class to do that, a simple example would be:
import threading
lock = threading.Lock()
//here it will be lock
lock.acquire() # will block if lock is already held
...
then in other side do
//this will wake up
lock.release()
you can read more here http://effbot.org/zone/thread-synchronization.htm

How to return value from function running by QThread and Queue

Please explain how do we send/receive data from Thread managed by Queue....
First I subclass 'QThread' defining its run() method which is started when QThread's.start() is called:
class SimpleThread(QtCore.QThread):
def __init__(self, queue, parent=None):
QtCore.QThread.__init__(self, parent)
self.queue=queue
def run(self):
while True:
arg=self.queue.get()
self.fun(arg)
self.queue.task_done()
def fun(self, arg):
for i in range (3):
print 'fun: %s'%i
self.sleep(1)
return arg+1
Then I declare two Thread instances (so only two CPU cores are taken) sending self.queue instance as an argument.
self.queue=queue.Queue()
for i in range(2):
thread=SimpleThread(self.queue)
thread.start()
Now if I understand it correctly thread.start() is not starting anything. The real "start" happens only when I call queue.put():
for arg in [1,2,3]: self.queue.put(arg)
This last line is what makes a "real" call. Aside from creating and starting Queue item put() allows to save any arbitrary value to each Queue item. .put() does several things at once: it creates, it starts, it moves the processing through the Queue and it allows to place a variable "inside" of the queue item (which later can be retrieved from inside of the function-processor: using Queue item's '.get()` method).
But how do I return the value from fun() function. A "regular" fun()'s return resultValue doesn't work. And I can't use self.queue.put() method since this method aside from storing a data "creates" a new queue item...
EDITED LATER:
Here is slightly tweaked code (copy/pasted from another post) showing an approach on how to return a value from completed Thread. I am not sure if the the approach used here would work with QThread... please correct me if I am wrong:
import os, sys
import threading
import Queue
def callMe(incomingFun, daemon=False):
def execute(_queue, *args, **kwargs):
result=incomingFun(*args, **kwargs)
_queue.put(result)
def wrap(*args, **kwargs):
_queue=Queue.Queue()
_thread=threading.Thread(target=execute, args=(_queue,)+args, kwargs=kwargs)
_thread.daemon=daemon
_thread.start()
_thread.result_queue=_queue
return _thread
return wrap
#callMe
def localFunc(x):
import time
x = x + 5
time.sleep(5)
return x
thread=localFunc(10)
# this blocks, waiting for the result
result = thread.result_queue.get()
print result
In normal circumstances you'd use a result queue to send results back, and then have some other thread running that waits for the results:
class SimpleThread(QtCore.QThread):
def __init__(self, queue, result_queue, parent=None):
QtCore.QThread.__init__(self, parent)
self.queue=queue
self.result_queue = result_queue
def run(self):
while True:
arg=self.queue.get()
self.fun(arg)
self.queue.task_done()
def fun(self, arg):
for i in range (3):
print 'fun: %s'%i
self.sleep(1)
self.result_queue.put(arg+1)
def handle_results(result_queue):
while True:
result = result_queue.get()
print("Got result {}".format(result))
Main thread:
self.queue=queue.Queue()
self.result_queue = queue.Queue()
result_handler = threading.Thread(target=handle_results, self.result_queue)
for i in range(2):
thread=SimpleThread(self.queue, self.result_queue)
thread.start()
Doing it this way will keep you from blocking the GUI's event loop while you wait for the results. Here's what the equivalent would look like with multiprocessing.pool.ThreadPool:
from multiprocessing.pool import ThreadPool
import time
def fun(arg):
for i in range (3):
print 'fun: %s'%i
time.sleep(1)
return arg+1
def handle_result(result):
print("got result {}".format(result))
pool = ThreadPool(2)
pool.map_async(fun, [1,2,3], callback=handle_result)
Which is a lot simpler. It internally creates a result handling thread, which will automatically call handle_result for you when fun completes.
That said, you're using QThread, and you want the results to update GUI widgets, so you really want your results to be sent back to the main thread, not to a result handling thread. In that case, it makes sense to use Qt's signaling system, so that you can safely update the GUI when you receive the result:
from PyQt4 import QtCore, QtGui
import sys
import Queue as queue
class ResultObj(QtCore.QObject):
def __init__(self, val):
self.val = val
class SimpleThread(QtCore.QThread):
finished = QtCore.pyqtSignal(object)
def __init__(self, queue, callback, parent=None):
QtCore.QThread.__init__(self, parent)
self.queue = queue
self.finished.connect(callback)
def run(self):
while True:
arg = self.queue.get()
if arg is None: # None means exit
print("Shutting down")
return
self.fun(arg)
def fun(self, arg):
for i in range(3):
print 'fun: %s' % i
self.sleep(1)
self.finished.emit(ResultObj(arg+1))
class AppWindow(QtGui.QMainWindow):
def __init__(self):
super(AppWindow, self).__init__()
mainWidget = QtGui.QWidget()
self.setCentralWidget(mainWidget)
mainLayout = QtGui.QVBoxLayout()
mainWidget.setLayout(mainLayout)
button = QtGui.QPushButton('Process')
button.clicked.connect(self.process)
mainLayout.addWidget(button)
def handle_result(self, result):
val = result.val
print("got val {}".format(val))
# You can update the UI from here.
def process(self):
MAX_CORES=2
self.queue = queue.Queue()
self.threads = []
for i in range(MAX_CORES):
thread = SimpleThread(self.queue, self.handle_result)
self.threads.append(thread)
thread.start()
for arg in [1,2,3]:
self.queue.put(arg)
for _ in range(MAX_CORES): # Tell the workers to shut down
self.queue.put(None)
app = QtGui.QApplication([])
window = AppWindow()
window.show()
sys.exit(app.exec_())
Output when the button is pushed:
fun: 0
fun: 0
fun: 1
fun: 1
fun: 2
fun: 2
fun: 0
got val 2
got val 3
Shutting down
fun: 1
fun: 2
Shutting down
got val 4

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

Accessing a non-global variable in Python

I'm trying to change the state of a Gtk status icon from a thread as specified in MailThread.run() below, but I don't know how to reach the status icon object from the method in order to change set_visible to either True or False.
Basically I would like to know what to write in place of "# set status icon visible off/on".
#!/usr/bin/env python
import gtk, sys, pynotify, imaplib, time, threading
from email import parser
class Mail:
def check_mail(self):
obj = imaplib.IMAP4_SSL('imap.gmail.com','993')
acc = 'email'
pwrd = 'pass'
obj.login(acc, pwrd)
obj.select()
num = str(len(obj.search(None,'UnSeen')[1][0].split()))
return acc, num
class MailThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
gtk.gdk.threads_init()
def run(self):
while True:
print "hello"
mail = Mail()
num = mail.check_mail()[1]
if num < 1:
# set status icon visible off
else:
# set status icon visible on
time.sleep(60)
class StatusIcon:
# activate callback
def activate( self, widget, data=None):
mail = Mail()
acc, num = mail.check_mail()
pynotify.init("myapp")
n = pynotify.Notification(acc, "You have " + num + " unread e-mails.", "emblem-mail")
n.show()
# Show_Hide callback
def show_hide(self, widget,response_id, data= None):
if response_id == gtk.RESPONSE_YES:
widget.hide()
else:
widget.hide()
# destroyer callback
def destroyer(self, widget,response_id, data= None):
if response_id == gtk.RESPONSE_OK:
gtk.main_quit()
else:
widget.hide()
# popup callback
def popup(self, button, widget, data=None):
dialog = gtk.MessageDialog(
parent = None,
flags = gtk.DIALOG_DESTROY_WITH_PARENT,
type = gtk.MESSAGE_INFO,
buttons = gtk.BUTTONS_OK_CANCEL,
message_format = "Do you want to close e-mail notifications?")
dialog.set_title('Exit')
dialog.connect('response', self.destroyer)
dialog.show()
def __init__(self):
# create a new Status Icon
self.staticon = gtk.StatusIcon()
self.staticon.set_from_icon_name("emblem-mail")
self.staticon.connect("activate", self.activate)
self.staticon.connect("popup_menu", self.popup)
self.staticon.set_visible(True)
# starting thread
thread = MailThread()
thread.setDaemon(True)
thread.start()
# invoking the main()
gtk.main()
if __name__ == "__main__":
# status icon
statusicon = StatusIcon()
You can accept the status icon in the thread's __init__():
class MailThread(threading.Thread):
def __init__(self, status_icon = None):
threading.Thread.__init__(self)
gtk.gdk.threads_init()
self.status_icon = status_icon
And then you can use it in run().
Additionally, you need to do all the GUI work from the main thread. The main thread has a queue maintained by GTK you can use to tell it to go do some GUI work. This is how it works:
def run(self):
# <...>
if num < 1:
gobject.idle_add(self.set_status_icon, False)
else:
gobject.idle_add(self.set_status_icon, True)
# <...>
def set_status_icon(self, state = False):
# code that changes icon state goes here
pass
idle_add basically means "add that to the queue and do it when you have some free time".

Categories

Resources