Home > Software design >  How to sync start of two cyclic tasks (swift)
How to sync start of two cyclic tasks (swift)

Time:02-01

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) ?

CodePudding user response:

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

  1. The most interesting data to analyse is up until Operation A10 done as after this A no longer runs

  2. 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

  1. Later on in the sequence, it also shows you what happens when B is shorter:
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

  1. I feel this could work for you because it does not matter if B is shorter or longer than A because B will be dependant on the current A to finish executing before starting again
  •  Tags:  
  • Related