Avoiding Cross-Checks

I have several jobs to run (pseudocode):

public bool IsJob1Running; public void DoJob1(...) { IsJob1Running = true; ... ; IsJob1Running = false; } public bool IsJob2Running; public void DoJob2(...) { IsJob2Running = true; ... ; IsJob2Running = false; } ... 

In some cases, only one task can be performed at a time, in others - several can be started, but others should not be started (you should wait or refuse to start). All this at some point leads to such monstrous checks:

 if(!IsJob1Running && !IsJob2Running && !IsJob3Running ... ) { ... } 

Everything suddenly appears in the software: when a user button is pressed (or even before turning off) before starting work, even inside DoJob , etc.

I hate this. Imagine a case where you need to add a new Job99 . Then, everywhere in the software, all checks must be updated to include Job99 checks.

My question is: is there an existing template for defining cross-checks of this kind (relationships?) That will make it easy to add new tasks, a centralized overview of all dependencies, etc.?

Edit

To give an example:

Work 1, 2, 3 can be performed at the same time, but not during task 4 (you need to check whether task 4 is performed before starting 1, 2 or 3 and vice versa). Then there are tasks 5, 6 and 7, only one can start when task 5 is called from task 1, it cannot be called from task 2.

+6
source share
3 answers

You can implement something like the base class of a job:

 public abstract class BaseJob { public bool IsRunning { get; private set; } public void Run() { IsRunning = true; RunInner(); IsRunning = false; } public abstract void RunInner(); } 

and then inherit all your assignments from this:

 public class LoadUserDataJob : BaseJob { public override void RunInner() { // load user data } } 

Then you can have a list of actions on your tasks:

 // Check if there is any running task if (jobsArray.Any(j => j.IsRunning)) // Check if there is a task of type LoadUserDataJob running // Use Where->Any instead of Single if there are many jobs of this type if (jobsArray.Where(j => j is LoadUserDataJob).Any(j => j.IsRunning)) 

You can also combine this with some Task and use Task.WaitAny , Task.WaitAll to ensure that it Task.WaitAll for execution.

Speaking of a universal structure or template that automatically detects and checks the dependencies, sequences and order of execution, then I can’t imagine it - it greatly depends on your business logic and the types of your tasks.

+1
source

Configuring dependencies between tasks is a separate problem, but you can expand your base class to include a static list of all tasks, as well as several instance properties that describe under what conditions a particular task should be performed. For instance:

 // All instantiated jobs should be added to this list, regardless of whether they're running. public static List<BaseJob> AllJobs { get; private set; } // These jobs must be running for the current job to start: public List<BaseJob> MustBeRunning { get; private set; } // These jobs must not be running for the current job to start: public List<BaseJob> CannotBeRunning { get; private set; } // This overrides the previous two lists to indicate whether the // job must not run concurrently with any other job (prevents you // from having to add every other job to "CannotBeRunning": public bool MustRunIndependently { get; set; } // Update your run method to take all of this into consideration: public void Run() { if (MustRunIndependently) { if (AllJobs.Any(x => x.IsRunning)) { throw new InvalidOperationException("This job must run independently."); } } else if (MustBeRunning.Any(x => !x.IsRunning)) { throw new InvalidOperationException("Required concurrent jobs are not running."); } else if (CannotBeRunning.Any(x => x.IsRunning)) { throw new InvalidOperationException("Incompatible jobs are currently running."); } // If we made it here, then it okay to run the job. IsRunning = true; RunInner(); // overrided by inheritors to perform actual job work. IsRunning = false; } 
+1
source

This problem can be solved by implementing the task scheduler.

No matter what I do, it uses a scheduler with a pending job queue and a current list of tasks. The tasks will be placed in the waiting tasks in the order of receipt, and the scheduler will be responsible for checking whether the next waiting task is allowed to perform, looking at the tasks currently being performed and moving from the waiting task and launching it accordingly.

You will need a callback mechanism to signal the completion of tasks and should be removed from the execution list by the scheduler:

 public abstract class Job { public event EventHandler<MyFinishedJobEventArgs> JobFinished; public void Run() { var e = new MyFinishedJobEventArgs(...); OnRunJob(e); if (JobFinished != null) JobFinished(this, e); } protected abstract void OnRunJob(MyFinsishedJobEventArgs e); //Job logic goes here. } 

The hard part here, of course, is ensuring proper synchronization with decent performance.

Agreeing with the rules that determine which jobs can be started or not, if all of them are of the JobX type, which cannot be executed during the work of JobY and JobZ, then this can be easily described in the scheduler, tracking incompatibility of jobs; I would use Dictionary<Type, IEnumerable<Type>>

 void RegisterIncompatibility(Job job, IEnumerable<Job> incompatibleJobs) { incompatibilities.Add(job.GetType(), incompatibleJobs.Select(j => j.GetType()); } //I prefer registering jobs instead of types to somehow ensure type safety. 

Now the scheduler should simply check before starting the next task for any current incompatibility:

 private bool canRunJob(Job job) { //omitted null checks, contains key check, etc. if (executingJobs.Any(j => incompatible.Contains(j.GetType()))) return false; return true; } 

This should be simple enough if you keep the order of execution. That is, the first task assigned is the first task that will be executed, and it will block all other pending tasks until it is allowed to work.

If you need to skip tasks that are currently not allowed to run and perform other pending tasks until the conditions are met, everything will start to get hairy.

+1
source

All Articles