简体   繁体   中英

Consistency of DispatchTime.asyncafter

In my iPad app I am trying to get a tableView to scroll at pre-determined intervals. To do this I have created a piece of code that creates a closure for each scroll. However the closure is executed seconds later than when I wanted. Here is a simplified Playground which exemplifies the same problem.

import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

// Intervals at which the delayed closures should run
var lengths: [Double] = [0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 
    12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0]
var times: [UInt64] = []

let dispatchStartTime = DispatchTime.now()

for index in 0 ..< lengths.count
{
    let delayNanoSeconds = Int(lengths[index] * 1_000_000_000)
    let dispatchLength = dispatchStartTime + .nanoseconds(delayNanoSeconds)
    times.append(dispatchLength.rawValue)

    DispatchQueue.main.asyncAfter(deadline: dispatchLength)
    {
        let delay = Double(DispatchTime.now().rawValue - times[index])
        print(delay/1_000_000_000)
    }
}

Upon running this I received the following results: (similar results for other trials, and for the code running in my app)

0.01646185
0.190259978
0.000302805
0.59319351
0.732933723
0.938841374
1.08249717
1.326286228
8.6309e-05
0.00019526
0.000104861
2.19584049
0.196220036
2.000116042
0.000425943
0.000202886

I can tolerate an error of 50-100 ms. However from the results we can see that the error can be greater than 2 seconds, and it appears to grow with time/amount of closures. In my app there can be on the order of 100 of such intervals. Is it possible to achieve this level of specificity with asyncAfter or would an alternative such as one thread with a busy wait be a better solution?

I have implemented a single thread version that relies on mach_wait_until . This is probably overkill for accuracy, but it doesn't seem to have any strong negative effect on energy use.

import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

var timeInfo: mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
mach_timebase_info(&timeInfo)
// Intervals at which the delayed closures should run
var lengths: [Double] = [0.0, 2.0, 4.0,6.0,8.0,10.0,12.0,14.0,16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 30.0, 32.0, 34.0, 36.0, 38.0, 40.0, 42.0, 44.0, 46.0, 48.0, 50.0, 52.0, 54.0, 56.0, 58.0, 60.0, 62.0, 64.0, 66.0, 68.0, 70.0]

let startTime = mach_absolute_time()

DispatchQueue.global().async()
{
    for index in 0 ..< lengths.count
    {
        let delayTicks = UInt64(lengths[index] * 1_000_000_000) * UInt64(timeInfo.denom) / UInt64(timeInfo.numer)
        mach_wait_until(startTime + delayTicks)

        DispatchQueue.main.async()
        {
            // UI changes
        }
    }
}

mach_until_wait() does not seem to work in playgrounds but the code in my app works as intended and doesn't have any noticeable delay.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM