ios - 轮询 GCD 主队列,避免死锁
问题描述
我有一个带有多个线程的 iOS 应用程序。在后台线程中,我运行了一些第 3 方代码。3rd 方代码偶尔会调用:
dispatch_sync(dispatch_get_main_queue(), block);
回调必须是 _sync,因为它需要答案并且它需要在主线程上,因为它调用UIApplication
.
当我需要关闭后台线程时会出现问题。关闭源自 UI,它也必须是同步的。所以,我有时会看到死锁。
我试图通过这里描述的方法解决它。基本上在循环中调用 NSRunLoop,直到后台线程设置了一个标志。像这样:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
但是,它不起作用。我可以在调试器中看到后台线程挂在 dispatch_sync 调用上,即使 NSRunLoop runUntilDate 被多次调用。
这是后台线程的调用栈,被阻塞:
这是主线程的调用堆栈(它没有崩溃,只是在调试器中暂停):
我认为这一定与运行模式有关,但我不确定是什么以及如何解决这个问题。那么,当主线程忙于循环时,如何为 GCD 主队列提供服务呢?
解决方案
该NSRunLoop
技术可以在某些情况下工作:
- (void)start {
dispatch_sync(self.queue, ^{
[self finishInTenSeconds];
while (!self.isFinished) {
NSLog(@"loop");
[[NSRunLoop mainRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:1]];
}
});
}
- (void)finishInTenSeconds {
[NSTimer scheduledTimerWithTimeInterval:10 repeats:false block:^(NSTimer * _Nonnull timer) {
self.isFinished = true;
}];
}
但是这种run
在阻塞主线程的同时手动调用主运行循环的技术,本质上是一种混搭(而且有点不合时宜)。例如用 GCD 调用替换定时器finishInTenSeconds
,这个闭包永远不会被调用:
- (void)finishInTenSeconds {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.isFinished = true;
});
}
问题是while
带有run
调用的循环将允许运行循环处理事件,但它不会改变主线程被原始dispatch_sync
调用阻塞的事实。
请注意,您的案例中的细节无疑是不同的,但希望这说明了 kludge 引入的一般问题NSRunLoop
。
适当的解决方案是尽量减少同步调用。例如,如果您需要在后台队列上的任务完成后做某事,而不是同步调用,如下所示:
dispatch_sync(self.queue, ^{
[self foo];
});
[self bar];
您将改为异步分派,然后在完成后将后续代码异步分派回主队列:
dispatch_async(self.queue, ^{
[self foo];
dispatch_async(dispatch_get_main_queue(), ^{
[self bar];
});
});
推荐阅读
- php - Memcached 在 Web 浏览器中不起作用,但使用 php 命令工作
- python - 如何通过公共列组合 2 个熊猫数组
- r - Bioconductor、R 版本和本地安装
- java - 使用 Java 进行安全用户身份验证
- amazon-web-services - 有没有办法在 AWS ECS 中设置 DNS 选项?
- react-native-maps - 所有 firebase 库必须高于或低于 14.0.0 (react-native-maps & react-native-firebase)
- scala - 字符串在 Scala 中被错误地解析为日期时间格式
- angular - 为什么 Angular 调用我的函数……几十次?
- jenkins - 在多个阶段之间重用 Jenkins 中的代理(docker 容器)
- ssl - 旧 TLS1.0 客户端无法连接到自定义域上的 Google App Engine