Difference Between Dispatchsourcetimer, Timer and Asyncafter

Difference between DispatchSourceTimer, Timer and asyncAfter?

Timer is a Swift bridge of NSTimer, which goes back to NeXTSTEP, long, long before Grand Central Dispatch (GCD) and things like DispatchSourceTimer, which didn't come along until 10.6 (in the form of dispatch_source_set_timer) and dispatchAfter (in the form of dispatch_after).

NSTimer is based on the run loop, which was the primary way that concurrency was done until GCD. It's a cooperative concurrency system, designed primary to run on a single thread on a single core (though it can be expanded to multi-threaded environments).

While the run loop is still very important in Cocoa, it is no longer the primary, or even preferred, way to manage concurrency. Since 10.6, GCD has been the increasingly preferred approach (though adding a block-based NSTimer API in the 10.12 timeframe was a welcome modernization).

On the scale of 15 seconds, the efficiency differences are pretty irrelevant. That said, I don't understand your comment "A Timer keeps the CPU from going into the idle state." I don't believe that's true. The CPU will definitely still go into the idle state when waiting on an NSTimer to fire.

I would not set up a run loop just to run an NSTimer. You would be much better off scheduling it on the main runloop and then using DispatchQueue.async to do the actual work on some other queue.

As a broad rule, I use the highest-level tool that meets the need. Those are the ones that Apple is likely to optimize the best over time with me making the fewest changes. For example, NSTimer fire dates are automatically adjusted to improve energy efficiency. With DispatchSourceTimer, you get control over the leeway setting to get the same benefit, but it's up to you to set it (the default is zero, which has the worst energy impact). Of course, the reverse is also true. DispatchSourceTimer is the lowest level and gives you the most control, so if that's what you need, that's the one to use.

For your example, I'd personally probably use a Timer and just dispatch to the private queue as part of the block. But a DispatchSourceTimer would be completely appropriate.

asyncAfter is really a different thing, since it's always a one-shot. That's great if you want a one-shot, but it changes things if you want to repeat. If you just call asyncAfter in the block to repeat, it's going to be 15 seconds after the last time you finished, rather than being spaced 15 seconds apart. The former will tend to drift a bit late over time. The design question is this: if for some reason your task took 5 seconds to complete, would you want the next fire event to happen 15 seconds from the end of that, or would you want a constant 15 seconds between each fire event? Your choice there will determine which tool is correct.

As a slight note there, NSTimer events are always a little later than they are scheduled. GCD events with a leeway setting can be a little early or a little late. As a practical matter, there's no such thing as being "on time" (that's a period of zero length; you're not going to hit it). So the question is always whether you are promised to be late like NSTimer, or you might be early like GCD with leeway.

Should I use a unnamed Timer or DispatchasyncAfter to delay something 1 time?

DispatchQueue.main.asyncAfter is a GCD method and hence it is better in performance. Though NSTimer is a much more flexible where you can stop or pause the timer but I don't think you can cancel the DispatchQueue.main.asyncAfter or at least you have to write a wrapper.

Though in your case repeats is false it won't effect flexibility.

IOS - Difference between DispatchQueue.main.asyncAfter(deadline: .now()) and perform(_:with:afterDelay:) with 0 delay

I created this standalone test to explore this topic.

class ViewController: UIViewController {

@objc func test1(_ info: Any) {
guard let str = info as? String else { return }
sleep(1)
print(str)

if str != "selector 3" {
self.perform(#selector(test1), with: "selector 3", afterDelay: 0)
}

DispatchQueue.main.asyncAfter(deadline: .now()) {
sleep(1)
print("dispatch 4 queued by \(str)")
}

}


@IBAction func test(_ sender: UIButton) {
print("begin test")

self.perform(#selector(test1), with: "selector 1", afterDelay: 0)

DispatchQueue.main.asyncAfter(deadline: .now()) {
DispatchQueue.main.asyncAfter(deadline: .now()) {
sleep(1)
print("dispatch 3")
}
sleep(1)
print("dispatch 1")
}

self.perform(#selector(test1), with: "selector 2", afterDelay: 0)

DispatchQueue.main.asyncAfter(deadline: .now()) {
sleep(1)
print("dispatch 2")
}

print("end test")
}

}

Resulting output:

begin test
end test
dispatch 1
dispatch 2
selector 1
selector 2
dispatch 3
dispatch 4 queued by selector 1
dispatch 4 queued by selector 2
selector 3
selector 3
dispatch 4 queued by selector 3
dispatch 4 queued by selector 3

Things to observe:

  1. begin test and end test print before any other output showing that both perform(_:with:afterDelay:) and DispatchQueue.main.asyncAfter are queued and run later.
  2. The first two DispatchQueues run before the performs even though they are queued in a different order.
  3. All of the prints happen one second apart meaning they are all being run on the same queue waiting for the previous to finish.
  4. dispatch 3 doesn't jump ahead of selector 1 and selector 2 even though it is queued before dispatch 1 prints.

Conclusions:

  1. Both Dispatch.main.asyncAfter and perform(_:with:afterDelay:) queue their selector/closure to be performed later. Since you are running perform(_:with:afterDelay:) on the main thread, it uses the main queue for its scheduling.
  2. For some unknown (to me) reason, Dispatch.main.asyncAfter(0) calls are queued before perform(_:with:afterDelay:0) calls when queued in the same run loop. Note: if any delay at all is added to the Dispatch.main.asyncAfter, it will then be queued after the perform(_:with:afterDelay:). Try using .now() + .milliseconds(1), for example.
  3. performed as soon as possible is just another way to say they are queued and processed in queue order. Depending on how long the tasks take in the queue ahead, it might take many runs of the run loop before a task is finally handled.

why 'asyncAfter' is not act in exact time?

It's right there in the method name-- "asyncAfter". The method isn't guaranteed to run code at a specific time, it runs code after a specific time. That might be immediately after the time or it might be later on. You should think of the method as meaning "wait at least this long", not "run code at exactly this time".

If you need more precise timing, consider using Timer or else creating a DispatchSourceTimer.

DispatchQueue.main.asyncAfter with on/off switch

There are a few approaches:

  1. invalidate a Timer in deinit

    Its implementation might look like:

    class ResponseTimer {
    private weak var timer: Timer?

    func schedule() {
    timer = Timer.scheduledTimer(withTimeInterval: 8, repeats: false) { _ in // if you reference `self` in this block, make sure to include `[weak self]` capture list, too
    // do something
    }
    }

    func invalidate() {
    timer?.invalidate()
    }

    // you might want to make sure you `invalidate` this when it’s deallocated just in
    // case you accidentally had path of execution that failed to call `invalidate`.

    deinit {
    invalidate()
    }
    }

    And then you can do:

    var responseTimer: ResponseTimer?

    func getSomethingFromFirebase() {
    responseTimer = ResponseTimer()
    responseTimer.schedule()

    ref.observeSingleEvent(of: .value) { snapshot in
    responseTimer?.invalidate()

    //do other stuff
    }
    }
  2. Use asyncAfter with DispatchWorkItem, which you can cancel:

    class ResponseTimer {
    private var item: DispatchWorkItem?

    func schedule() {
    item = DispatchWorkItem { // [weak self] in // if you reference `self` in this block, uncomment this `[weak self]` capture list, too
    // do something
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: item!)
    }

    func invalidate() {
    item?.cancel()
    item = nil
    }

    deinit {
    invalidate()
    }
    }
  3. Use DispatchTimerSource, which cancels automatically when it falls out of scope:

    struct ResponseTimer {
    private var timer: DispatchSourceTimer?

    mutating func schedule() {
    timer = DispatchSource.makeTimerSource(queue: .main)
    timer?.setEventHandler { // [weak self] in // if you reference `self` in the closure, uncomment this
    NotificationCenter.default.post(name: notification, object: nil)
    }
    timer?.schedule(deadline: .now() + 8)
    timer?.activate()
    }

    mutating func invalidate() {
    timer = nil
    }
    }

In all three patterns, the timer will be canceled when ResponseTimer falls out of scope.

Timers conflicst in AUv3 in Audiokit when running some instances of the AUv3

The problem likely isn't the Timer -- it's the fact that you probably (since it's not shown here) are relying on some sort of singleton in your code.

AUv3 instances need to have truly unique instances of their dependencies to function independently.



Related Topics



Leave a reply



Submit