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]