Let me rephrase the requirements, because the term "consistent results" might be confusing.
#1 - Executed every 10 seconds. Executions should not overlap with #2, #3 or #4.
#2 - Executed every 2 hours. Executions should not overlap with #1, #4 or #5.
#3 - Executed every day at 10:00 and 22:00. Executions should not overlap with #1.
#4 - Executed every Sunday at 22:00 and every Friday at 22:00. Executions should not overlap with #1 or #2.
#5 - Executed every 1st day of the month. Executions should not overlap with #2. Should invoke #2 just before starting.
I think that the requirements can be met by using 5 Mutex
objects, one for each task. Then before starting a task you acquire its corresponding Mutex
, and all Mutex
es of the relevant mutually exclusive tasks, all at once as an atomic operation. After acquiring them, immediately release all the acquired Mutex
es except from the one that corresponds to the current task, and then start the task. Finally, when the task completes, release its own Mutex
as well.
So for example for the task #1, acquire the Mutex
es 1, 2, 3 and 4, then immediately release the Mutex
es 2, 3 and 4, and finally release the Mutex
1. This way the task #1 will not start while any of the #2, #3 or #4 are running, and while it's running it will prevent any of the #2, #3 or #4 from starting. Below is a complete implementation of this idea.
private static readonly Mutex _m1 = new();
private static readonly Mutex _m2 = new();
private static readonly Mutex _m3 = new();
private static readonly Mutex _m4 = new();
private static readonly Mutex _m5 = new();
private static void RunSynchronized1()
{
// Should not overlap with #2, #3 or #4.
Mutex self = _m1;
Mutex[] other = new Mutex[] { _m2, _m3, _m4 };
WaitHandle.WaitAll(other.Prepend(self).ToArray());
Array.ForEach(other, h => h.ReleaseMutex());
try { Run1(); } finally { self.ReleaseMutex(); }
}
private static void RunSynchronized2()
{
// Should not overlap with #1, #4 or #5.
Mutex self = _m2;
Mutex[] other = new Mutex[] { _m1, _m4, _m5 };
WaitHandle.WaitAll(other.Prepend(self).ToArray());
Array.ForEach(other, h => h.ReleaseMutex());
try { Run2(); } finally { self.ReleaseMutex(); }
}
private static void RunSynchronized3()
{
// Should not overlap with #1.
Mutex self = _m3;
Mutex[] other = new Mutex[] { _m1 };
WaitHandle.WaitAll(other.Prepend(self).ToArray());
Array.ForEach(other, h => h.ReleaseMutex());
try { Run3(); } finally { self.ReleaseMutex(); }
}
private static void RunSynchronized4()
{
// Should not overlap with #1 or #2.
Mutex self = _m4;
Mutex[] other = new Mutex[] { _m1, _m2 };
WaitHandle.WaitAll(other.Prepend(self).ToArray());
Array.ForEach(other, h => h.ReleaseMutex());
try { Run4(); } finally { self.ReleaseMutex(); }
}
private static void RunSynchronized5()
{
// Should not overlap with #2. Should invoke #2 just before starting.
RunSynchronized2();
Mutex self = _m5;
Mutex[] other = new Mutex[] { _m2 };
WaitHandle.WaitAll(other.Prepend(self).ToArray());
Array.ForEach(other, h => h.ReleaseMutex());
try { Run5(); } finally { self.ReleaseMutex(); }
}
The 5 tasks are represented by the methods Run1
, Run2
, Run3
, Run4
and Run5
, whose definition is omitted. The RunSynchronized1
is the synchronized version of Run1
, etc. You can use a timer component like the PeriodicTimer
to schedule the RunSynchronized1
every 10 seconds, the RunSynchronized2
every 2 hours, etc (example). For top of the minute scheduling you could use the CronosPeriodicTimer
that I have posted as an answer to a relevant question.
The above setup assumes that your program will be running continuously for months. If you don't like this idea, and you would prefer to let the Windows Scheduler start your program for executing just a single task each time, you would have to do some changes. You would need to instantiate the Mutex
es with the constructor that has a name
parameter (named mutex), giving a unique name to each instance, so that they are effective across process boundaries. You would also need to change the RunSynchronized1
that is invoked frequently, passing the TimeSpan.Zero
as second parameter of the WaitHandle.WaitAll
, and exiting the program if the result is false
. Otherwise you may end up with a ton of instances of your program waiting their turn to execute the Run1
task.