首页 > 解决方案 > 轮询 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 主队列提供服务呢?

标签: iosmultithreadinggrand-central-dispatchdeadlocknsrunloop

解决方案


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];
    });
});

推荐阅读