Implemented draft of new background task system
This commit is contained in:
parent
7c121cd142
commit
e4a78046d7
11 changed files with 370 additions and 159 deletions
|
|
@ -1,16 +1,10 @@
|
|||
from threading import Thread
|
||||
import datetime
|
||||
import logging
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
from openflexure_microscope.exceptions import TaskDeniedException
|
||||
from openflexure_microscope.utilities import entry_by_id
|
||||
from openflexure_microscope.common.tasks import tasks, states, taskify, cleanup_tasks, remove_task
|
||||
|
||||
|
||||
class TaskOrchestrator:
|
||||
"""
|
||||
Class responsible for spawning threaded tasks, and storing their returns.
|
||||
DEPRECATED: Class responsible for spawning threaded tasks, and storing their returns.
|
||||
A microscope should contain exactly one instance of `TaskOrchestrator`.
|
||||
|
||||
Attributes:
|
||||
|
|
@ -18,26 +12,24 @@ class TaskOrchestrator:
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.tasks = []
|
||||
@property
|
||||
def tasks(self):
|
||||
logging.warning("TaskOrchestrator class is deprecated. Please use common.tasks.tasks().values() instead.")
|
||||
return tasks().values()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""
|
||||
Returns a list of dictionary representations of all tasks in the session.
|
||||
"""
|
||||
state = {}
|
||||
|
||||
for task in self.tasks:
|
||||
state[task.id] = task.state
|
||||
|
||||
return state
|
||||
logging.warning("TaskOrchestrator class is deprecated. Please use common.tasks.tasks() instead.")
|
||||
return states()
|
||||
|
||||
def task_from_id(self, task_id: str):
|
||||
"""
|
||||
Returns a particular task object with the specified `task_id`.
|
||||
"""
|
||||
return entry_by_id(task_id, self.tasks)
|
||||
return tasks()[task_id]
|
||||
|
||||
def start(self, function, *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -49,119 +41,20 @@ class TaskOrchestrator:
|
|||
function (function): The target function to run in the background
|
||||
args, kwargs: Arguments that will be passed to `function` at runtime.
|
||||
"""
|
||||
# Create a task object
|
||||
task = Task(function, *args, **kwargs)
|
||||
|
||||
# If the task isn't allowed to run
|
||||
if not self.start_condition(task):
|
||||
raise TaskDeniedException("Unable to start this task. Aborting.")
|
||||
else:
|
||||
self.tasks.append(task)
|
||||
task.start()
|
||||
return task
|
||||
logging.warning("TaskOrchestrator class is deprecated. Please use common.tasks.taskify() instead.")
|
||||
return taskify(function)(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Remove all inactive tasks from the task array.
|
||||
This will remove tasks that either finished or never started.
|
||||
"""
|
||||
self.tasks = [task for task in self.tasks if task._running]
|
||||
logging.warning("TaskOrchestrator class is deprecated. Please use common.tasks.cleanup_tasks() instead.")
|
||||
cleanup_tasks()
|
||||
|
||||
def delete(self, task_id):
|
||||
def delete(self, task_id: str):
|
||||
"""
|
||||
Delete a given task by ID, only if task is not currently running.
|
||||
"""
|
||||
success = False
|
||||
|
||||
for task in self.tasks:
|
||||
if task.id == task_id and not task._running:
|
||||
self.tasks.remove(task)
|
||||
success = True
|
||||
|
||||
return success
|
||||
|
||||
def start_condition(self, task_obj):
|
||||
"""
|
||||
Method returning bool describing if a particular task is allowed to run.
|
||||
Currently always allows tasks to start, as locks determine access to hardware.
|
||||
"""
|
||||
# return not any([task._running for task in self.tasks])
|
||||
return True
|
||||
|
||||
|
||||
class Task:
|
||||
"""
|
||||
Class responsible for running a task function in a thread, and handling return or errors.
|
||||
Tasks should be created by an instance of :py:class:`openflexure_microscope.task.TaskOrchestrator`.
|
||||
|
||||
Args:
|
||||
function (function): Function to be called in the task thread.
|
||||
|
||||
Attributes:
|
||||
task (function): Function to be called in the task thread.
|
||||
args: Positional arguments to be passed to the task function
|
||||
kwargs: Keyword arguments to be passed to the task function.
|
||||
_running (bool): If task is currently running
|
||||
id (str): Unique ID for the task
|
||||
state (dict): Dictionary describing the full state of the task.
|
||||
`status` will be one of 'idle'', 'running', 'error', or 'success'.
|
||||
`return` eventually holds the return value of the task function.
|
||||
"""
|
||||
|
||||
def __init__(self, function, *args, **kwargs):
|
||||
# The task long-running method
|
||||
self.task = function
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
self._running = False
|
||||
|
||||
self.id = uuid.uuid4().hex
|
||||
|
||||
self.state = {
|
||||
"id": self.id,
|
||||
"status": "idle",
|
||||
"return": None,
|
||||
"start_time": None,
|
||||
"end_time": None,
|
||||
}
|
||||
|
||||
def process(self, f):
|
||||
"""
|
||||
Wraps the target function to handle recording `status` and `return` to `state`.
|
||||
"""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
self.state["status"] = "running"
|
||||
try:
|
||||
r = f(*args, **kwargs)
|
||||
s = "success"
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.error(traceback.format_exc())
|
||||
r = str(e)
|
||||
s = "error"
|
||||
self.state["return"] = r
|
||||
self.state["status"] = s
|
||||
|
||||
return wrapped
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Records the task start and end times to `state`, and runs the task wrapped in `process`.
|
||||
"""
|
||||
self.state["start_time"] = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
self.process(self.task)(*self.args, **self.kwargs)
|
||||
self._running = False
|
||||
self.state["end_time"] = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
|
||||
def start(self): # Start and draw
|
||||
"""
|
||||
Spawns a thread, and starts `run()` in that thread.
|
||||
"""
|
||||
# If not already running
|
||||
if not self._running:
|
||||
self._running = True # Set start command
|
||||
thread = Thread(target=self.run) # Define thread
|
||||
thread.daemon = True # Stop this thread when main thread closes
|
||||
thread.start() # Start thread
|
||||
logging.warning("TaskOrchestrator class is deprecated. Please use common.tasks.remove_task() instead.")
|
||||
remove_task(task_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue