【问题标题】:Why does GCD dispatch_async on main queue causes a deadlock from background queue?为什么主队列上的 GCD dispatch_async 会导致后台队列死锁?
【发布时间】:2024-01-21 20:10:01
【问题描述】:

我正在创建一个这样的串行后台队列:

@property (nonatomic, strong) dispatch_queue_t assetCreationQueue;

// in init...
_assetCreationQueue = dispatch_queue_create("com.mycompany.assetCreationQueue", DISPATCH_QUEUE_SERIAL);

然后我像这样在后台枚举 ALAsset 对象:

[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
    if (asset){
        dispatch_async(weakSelf.assetCreationQueue, ^{
            ALAssetRepresentation *assetRepresentation = [asset defaultRepresentation];
            NSURL *url = [assetRepresentation url];
            if (url) {
                AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:url options:nil];

                // This NSLog fires! avAsset exists.
                NSLog(@"AVURLAsset %@", avAsset);

                dispatch_async(dispatch_get_main_queue(), ^{
                    // This NSLog NEVER fires.
                    // Also tried dispatch_sync.
                    NSLog(@"add to assets array on main queue");
                    [weakSelf.assets insertObject:avAsset atIndex:0];
                });
            }
        });
    }
}];

assets数组属性定义为:

@property (nonatomic, strong) NSMutableArray *assets;

当我尝试dispatch_sync(dispatch_get_main_queue(), ^{ 时,我在控制台中只得到一个NSLog(@"AVURLAsset %@", avAsset);,这表明dispatch_sync 正在导致死锁。

但是我怎样才能找出原因呢?我看不出在哪里。 assetsCreationQueue 是一个后台队列,我只能在主队列上对数组进行操作。

编辑: 这是一个更加简化的测试,它也失败了:

[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
    if (asset){
        dispatch_async(weakSelf.assetCreationQueue, ^{
            if ([NSThread isMainThread]) {
                NSLog(@"already main thread"); // gets called often!!
            } else {
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"dispatch async on main queue"); // never gets called!!
                });
            }
        });
     }
}];

所以我不明白的是:为什么我已经在主线程上,即使我调用了dispatch_async(weakSelf.assetCreationQueue。它只能导致邪恶的结论:我创建的队列不是后台队列:

_assetCreationQueue = dispatch_queue_create("com.mycompany.assetCreationQueue", DISPATCH_QUEUE_SERIAL);

为什么?

【问题讨论】:

  • 您能否再次检查更新后的代码是否是您实际测试的代码?它缺少一个括号。
  • @RobNapier 已修复。当我尝试在 SO 代码编辑器中对其进行格式化时,它被意外删除了。这是我正在测试的代码。
  • 看起来你的后台队列和主队列是一回事。你能注销weakSelf.assetCreationQueueNSLog(@"%p", queue); 返回的内容来确认/反驳吗?
  • 不幸的是,我在这段代码上搞砸了很多,因为帖子现在完全不同了。但似乎 AVAssetLibrary -enumerateAssetsUsingBlock 方法正在使用 dispatch_sync(dispatch_get_main_queue(),...) 如果我尝试在主队列上分派块,则会导致死锁。

标签: iphone ios objective-c concurrency grand-central-dispatch


【解决方案1】:

(不是真正的答案,但我想提供足够的信息来继续前进。)

我尝试了一个非常简单的程序,但它根本无法重现。我收到很多关于“在主队列上调度异步”的电话,而没有对“已经是主线程”的电话(与您所期望的相反)。我在 iPhone 5 上运行它。完整代码:

#import "AppDelegate.h"
@import AssetsLibrary;

@interface AppDelegate ()
@property (nonatomic, strong) dispatch_queue_t assetCreationQueue;
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  self.assetCreationQueue = dispatch_queue_create("com.mycompany.assetCreationQueue", DISPATCH_QUEUE_SERIAL);

  ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
  AppDelegate __weak *weakSelf = self;
  [library enumerateGroupsWithTypes:ALAssetsGroupAll
                         usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
                           [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
                             if (asset){
                               dispatch_async(weakSelf.assetCreationQueue, ^{
                                 if ([NSThread isMainThread]) {
                                   NSLog(@"already main thread"); // gets called often!!
                                 } else {
                                   dispatch_async(dispatch_get_main_queue(), ^{
                                     NSLog(@"dispatch async on main queue"); // never gets called!!
                                   });
                                 }
                               });
                             }
                           }];
                         }
                       failureBlock:nil];
  return YES;
}
@end

请注意,在这里使用weakSelf 是不必要且危险的(但如果这是问题所在,您会崩溃)。如果要将该块存储在self 保留的对象的属性中,则只需要weakSelf。像这样的短命块没有必要甚至不需要。您使用它的方式,如果在枚举中间释放拥有对象(它可以),weakSelf 将变为 nil,weakSelf.assetCreationQueue 将变为 nil,并且dispatch_async() 将崩溃。 (但同样,这不太可能是您的问题的原因,因为它应该会崩溃。)

【讨论】:

  • 谢谢 Rob,但运气不好。在我的情况下,由 AVAssetLibrary 枚举器调用的所有块都已经在主线程上执行。我做了更多的测试并从串行队列转移到并发队列,并发现当我调用 dispatch_sync(mainQueue,...) 时,它调用 dispatch_async(concurrentQueue,...),它要么调用 dispatch_async(mainQueue,... ) 或 dispatch_sync(mainQueue,...),出现死锁。
  • 你的意思是上面的代码本身不能在你的设备上运行?你运行的是什么版本的 iOS?
  • 哦,现在我明白了!!!!我不知道为什么或如何,但突然之间,您的代码在我的 iPhone 5 iOS 7 上完美运行。起初它似乎也陷入了僵局。我从我的原始代码中认为的罪魁祸首是有一个 dispatch_async 调用意外地变成了一个 dispatch_sync。因为已经是主线程,dispatch_sync 将其死锁。谢谢x1000!接受你的回答。