I have two loops that must run periodically.
One of the loop (A) has a constant duration and is scheduled with high accuracy, no problem.
The other loop (B) can have a variable duration. Much shorter or much longer than duration of A. But whatever B’s duration, I want to start next B cycle at the start of the next A cycle. If B is much shorter than A, then it must wait next A’start. If B is much longer than A, then, when it completes, whatever the number of A cycles that occurred, it shall wait and start at next A’s start.
I first thought of using a semaphore. A would signal the semaphore at its start, while B would wait for it at it start. This works if B is shorter than A.
But if B is longer, the semaphore gets increment (maybe over 1), and I don’t get B to wait, it restarts immediately at its end and starts-sync is lost.
Any solution (preferably with GCD, but any solution would do) ?
Since you have an idea of using GCD and semaphores as a solution, I feel something like this could also be achieved with OperationQueues
I like the use of OperationQueues
for your use case because you can add dependencies like in the situation when B is longer and has to wait for the current A to finish.
Here is a small experiment I ran, please have a look if this suits your use case:
Logic with comments
// A duration is fixed at 5
let aDuration: UInt32 = 5
// Control B's duration between 1 and 15
let maxBDuration: UInt32 = 15
// Initialize an Operation queue
let operationQueue = OperationQueue()
// Operations A and B which will be initialized
// before being added to a queue
var operationA: BlockOperation!
var operationB: BlockOperation!
// Counters to identify which loop A and B currently
// are in, just for our identification
var aLoopCount = 0
var bLoopCount = 0
// When to stop the experiment
let experimentCount = 10
// Launch the A loop
runALoop()
// Launch the B loop simultaneously specifying
// it is first launch, you will see why soon
runBLoop(isFirstLaunch: true)
// B is identical to A with a few small differences
private func runBLoop(isFirstLaunch: Bool = false)
{
operationB = BlockOperation
{
self.bLoopCount += 1
// The duration of b varies as you mentioned
let duration = UInt32.random(in: 1...self.maxBDuration)
print("Operation B\(self.bLoopCount) started, Duration: \(duration)s")
sleep(duration)
print("Operation B\(self.bLoopCount) done")
if self.bLoopCount != self.experimentCount
{
self.runBLoop()
}
}
// Check if we are not in the first launch
if !isFirstLaunch
{
// Add a dependency of the newly initialize B loop
// To the currently running A so it starts only when
// the current A finishes
operationB.addDependency(operationA)
}
operationQueue.addOperation(operationB)
}
The Output
Operation A1 started, Duration: 5s
Operation B1 started, Duration: 14s
Operation A1 done
Operation A2 started, Duration: 5s
Operation A2 done
Operation A3 started, Duration: 5s
Operation B1 done
Operation A3 done
Operation A4 started, Duration: 5s
Operation B2 started, Duration: 13s
Operation A4 done
Operation A5 started, Duration: 5s
Operation A5 done
Operation A6 started, Duration: 5s
Operation B2 done
Operation A6 done
Operation A7 started, Duration: 5s
Operation B3 started, Duration: 8s
Operation A7 done
Operation A8 started, Duration: 5s
Operation B3 done
Operation A8 done
Operation B4 started, Duration: 4s
Operation A9 started, Duration: 5s
Operation B4 done
Operation A9 done
Operation B5 started, Duration: 3s
Operation A10 started, Duration: 5s
Operation B5 done
Operation A10 done
Operation B6 started, Duration: 7s
Operation B6 done
Operation B7 started, Duration: 5s
Operation B7 done
Operation B8 started, Duration: 13s
Operation B8 done
Operation B9 started, Duration: 3s
Operation B9 done
Operation B10 started, Duration: 8s
Operation B10 done
Conclusions
The most interesting data to analyse is up until Operation A10 done as after this A no longer runs
This output sequence shows you the case when B is longer:
Operation A1 started, Duration: 5s
Operation B1 started, Duration: 14s
Operation A1 done
Operation A2 started, Duration: 5s
Operation A2 done
Operation A3 started, Duration: 5s
Operation B1 done
Operation A3 done
Operation A4 started, Duration: 5s
Operation B2 started, Duration: 13s
As you see B1
starts when A1
starts and ends before A3
is done but it waits till A3
before it starts again with A4
Operation B4 started, Duration: 4s
Operation A9 started, Duration: 5s
Operation B4 done
Operation A9 done
Operation B5 started, Duration: 3s
Operation A10 started, Duration: 5s
Operation B5 done
Operation A10 done
Operation B6 started, Duration: 7s
In this situation, Both B4 and B5 end before the current A operation is finished but they wait