OS X平台后台运行时Qt应用线程休眠时长异常
解决OS X后台线程休眠精度异常的问题
这个问题我之前做OS X后台任务时也踩过坑!罪魁祸首就是苹果的App Nap特性——为了省电,系统会给后台闲置的应用降低CPU调度优先级,大概一分钟左右就会让它进入低功耗状态,这时候普通的sleep()或者基础定时器的精度会被大幅压缩,甚至出现几秒的延迟,完全达不到你要的100ms间隔要求。
下面给你几个可行的解决方案,按实用性排序:
1. 临时禁用App Nap(最直接的方案)
你可以主动告诉系统:“我的应用正在做重要的后台任务,别让我休眠!”,有两种实现方式:
方式一:通过Info.plist全局禁用
在应用的Info.plist里添加一个键值对:
- 键:
NSAppSleepDisabled - 值:
YES(布尔类型)
这种方式一劳永逸,但会让应用全程无法进入App Nap,可能增加耗电量,适合确实需要持续后台运行的场景。
方式二:代码中动态控制(推荐)
更灵活的方式是在数据采集开始时阻止App Nap,结束后再恢复正常:
// 定义全局变量保存activity标识 id _activityToken = nil; // 开始采集时调用 - (void)startDataCollection { _activityToken = [[NSProcessInfo processInfo] beginActivityWithOptions: NSActivityIdleSystemSleepDisabled | NSActivityUserInitiatedAllowingIdleSystemSleep reason:@"持续进行数据采集任务"]; } // 结束采集时调用 - (void)stopDataCollection { if (_activityToken) { [[NSProcessInfo processInfo] endActivity:_activityToken]; _activityToken = nil; } }
这个方法只会在采集期间阻止系统休眠,平时仍能正常省电,是最优选择。
2. 用高精度定时器替代sleep()
如果不想修改App Nap状态,你可以换用GCD定时器,它的精度比sleep()高很多,能更好地和系统调度配合:
// 创建串行队列用于采集任务 dispatch_queue_t dataQueue = dispatch_queue_create("com.yourapp.data-collection", DISPATCH_QUEUE_SERIAL); // 创建定时器源 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dataQueue); // 设置100ms间隔,允许1ms误差 uint64_t interval = 100 * NSEC_PER_MSEC; dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, 1 * NSEC_PER_MSEC); // 设置采集回调 dispatch_source_set_event_handler(timer, ^{ // 这里写你的数据采集逻辑 std::cout << "完成一次数据采集" << std::endl; }); // 启动定时器 dispatch_resume(timer);
如果需要极致精度,可以加上DISPATCH_TIMER_STRICT选项(但会增加耗电):
dispatch_source_set_flags(timer, DISPATCH_TIMER_STRICT);
3. Qt应用的适配方案
如果你是用Qt开发的,除了上面的Cocoa原生方法,还可以结合Qt API优化:
- 使用高精度
QTimer:
QTimer* collectTimer = new QTimer(this); collectTimer->setTimerType(Qt::PreciseTimer); collectTimer->setInterval(100); connect(collectTimer, &QTimer::timeout, this, [=](){ // 执行数据采集逻辑 }); collectTimer->start();
- 同时在Qt代码中调用Cocoa的activity设置(需要将文件后缀改为
.mm,启用ObjC++):
#include <objc/objc.h> #include <objc/message.h> void startPreventAppNap() { id processInfo = objc_msgSend(objc_getClass("NSProcessInfo"), sel_getUid("processInfo")); SEL beginActivitySel = sel_getUid("beginActivityWithOptions:reason:"); // 对应NSActivityIdleSystemSleepDisabled | NSActivityUserInitiatedAllowingIdleSystemSleep unsigned long options = (1 << 4) | (1 << 0); id reason = objc_msgSend(objc_getClass("NSString"), sel_getUid("stringWithUTF8String:"), "Qt数据采集任务"); objc_msgSend(processInfo, beginActivitySel, options, reason); }
验证方法
设置完成后,把应用切到后台,打开Console.app过滤你的应用日志,观察每次采集的时间间隔,应该能稳定在100ms左右,不会再出现几秒的延迟了。
内容的提问来源于stack exchange,提问作者Colin
相关产品推荐
相关产品推荐

