Source code for vdat.gui.background
"""Connection between the gui and the background thread to keep the gui
responsive as vdat works
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from functools import partial
import logging
from PyQt4.QtCore import QObject, QThread, pyqtSignal
from vdat.gui.fplane import VDATRunControl
_background = []
[docs]class Worker(QObject):
"""A worker object to stick into a QThread so it can run in the background
and allow the GUI to still be responsive.
Attributes
----------
finished : :class:`pyqtSignal`
qt signal
"""
# This is converted to an instance variable magically by __init_
finished = pyqtSignal()
def __init__(self, func, bg, immediate, *args, **kwargs):
"""
Parameters
----------
func : callable
function to execute
bg : Background object
The background the worker is running in
immediate : bool
True if job is on the immediate thread
args : list
positional arguments to pass to the function
kwargs : dictionary
keyword arguments to pass to the function
"""
super(Worker, self).__init__()
self.func = partial(func, *args, **kwargs)
self.bg = bg
self.immediate = immediate
[docs] def run(self):
"""Run the function! Then emit a signal telling the rest of the code
it's done.
"""
try:
result = self.func()
except Exception as e:
# finished = pyqtSignal()
log = logging.getLogger('logger')
log.exception("Unhandled exception on the background thread!"
" Please contact the programmers. Error : %s",
str(e))
result = None
if self.immediate:
self.bg.isImmRunning = False
else:
self.bg.isRunning = False
self.finished.emit()
return result
[docs]class Relay(QObject):
"""Signals have to be attached to an object. This object contain the signal
that is used to trigger Workers in the background thread.
Attributes
----------
signal : :class:`pyqtSignal`
qt signal
"""
signal = pyqtSignal()
def __init__(self, parent=None):
QObject.__init__(self, parent)
[docs] def emit(self):
self.signal.emit()
[docs] def connect(self, call):
self.signal.connect(call)
[docs]class Background(object):
"""Run a multiple QThreads in the background.
By doing this the GUI can remain response whilst the code is doing work.
Have to keep the same QThread throughout as the VDAT database has to be
queried from the Thread that created it.
The background needs to be given a queue to read jobs from.
"""
def __init__(self, parent=None):
self._queue = None
self.run_signal = Relay(parent=parent)
self.log = logging.getLogger('logger')
self.parent = parent
self.thread = QThread(parent=parent)
self.thread.start()
self.threadImmediate = QThread(parent=parent)
self.threadImmediate.start()
self.run_signal_imm = Relay(parent=parent)
self.isRunning = False
self.isImmRunning = False
@property
def queue(self):
return self._queue
@queue.setter
[docs] def queue(self, queue):
"""Set the queue that the background read jobs from. Connect the run()
method of the background to the job_added signal of the queue.
Parameters
----------
queue : :class:`vdat.gui.queue.Queue` object
The queue to read jobs from
"""
self._queue = queue
self._queue.job_added.connect(self.run)
[docs] def run_now(self, func, conf, *args, **kwargs):
"""Run a job on the immediate thread. These jobs don't queue but kick
off the old job and start up immediately (the functions have to be
specially designed, see below)
Parameters
----------
func : function
this function must start by setting VDATRunControl.ifu_loop = True
and stop when this variable is false
conf : vdat config object
"""
import time
VDATRunControl.ifu_loop = False # stop the current job
# Run the new one
self.workerImmediate = Worker(func, self, True, conf, *args, **kwargs)
self.workerImmediate.moveToThread(self.threadImmediate)
# wait for the last job to stop
while self.isImmRunning:
time.sleep(0.2)
# toggle the variable that tracks if it's running
self.isImmRunning = True # this is turned off by the worker when done
self.run_signal_imm.connect(self.workerImmediate.run)
self.run_signal_imm.emit()
[docs] def run(self, *args, **kwargs):
"""Grab a job from the queue and run it on the background thread if the
thread is not already busy. Works its way recursively through the
queue, until the queue is empty.
"""
if self.isRunning:
self.log.info("Added command to queue")
return
func = self.queue.get_command()
if not func:
self.log.info("All commands on queue completed!")
else:
self.log.debug("Launching new command...")
# Make a worker to run the function, move it to the background
# thread. The worker cannot be a local variable otherwise it goes
# out of scope as soon as we exit this function!
self.worker = Worker(func, self, False, *args, **kwargs)
self.worker.moveToThread(self.thread)
# Attach run_signal to the run() command of the worker
self.run_signal.connect(self.worker.run)
# Grab the next job, toggle isRunning
self.isRunning = True
self.worker.finished.connect(self.run)
# Start the function by emitting the signal
self.run_signal.emit()
[docs]def set_backgound(parent):
"""Set up a background instance
Parameters
----------
parent : :class:`QtWidget` instance
The QtWidget that the menu is attached to
"""
b = Background(parent=parent)
_background.append(b)
[docs]def get_background():
"""Returns the :class:`Background` instance created by :func:`set_backgound`
"""
return _background[0]