I'm creating a framework that needs to execute certain tasks, I'll use building a car as an example for this. So, an individual task could be something like weld metal
or screw bolt
. But there are also collections of tasks, I call them jobs. Each job can have X amount of tasks, so combining these two tasks could get us a job build an engine
:
build an engine
+-- weld metal
+-- screw bolts
Additionally, jobs can have sub-jobs:
build a car
+-- build an engine
| +-- weld metal
| +-- screw bolts
+-- build a frame
| +-- weld metal
| +-- screw bolts
| +-- paint
+-- combine engine and frame
So basically each node of my tree is a job, and each leaf is a task. This is easy enough, here's what my classes would look like:
class Task:
def do(self):
raise NotImplementedError('abstract method')
class Job(Task):
def __init__(self):
self.tasks = []
def do(self):
for task in self.tasks:
task.do()
And now I'd just subclass WeldMetal
and ScrewBolts
from Task
.
The problem arrives when we want to parallellise this. I need to somehow tell each task (and job) whether it should block the execution, or run in a separate thread and not block it. Sometimes my jobs also need results from other jobs, while those jobs may run in parallel to each other. For example in the above car building:
weld metal
must block, since you can't screw before the metal is welded.build an engine
andbuild a frame
can be ran in their own threads (and within those threads theweld metal
andscrew bolts
must block the particular thread).combine engine and frame
must wait forbuild an engine
andbuild a frame
to finish.
Honestly I'm not too sure where to start, initially I thought of using ThreadPoolExecutor
in Job.do()
, but I'm not sure how to block only some jobs and not others, and another issue is that sometimes a task must be ran "alone" without even being inside of any job, i.e. WeldMetal(block=False).do()
must be valid.
I ended up using this at first:
class Task:
def do(self):
raise NotImplementedError('abstract method')
def run(self):
if self.block:
self.do()
else:
threading.Thread(target=self.do).start()
And this works for most cases, but the issue comes when combine engine and frame
needs to wait for both build an engine
and build a frame
to finish.