为什么主队列上的GCD dispatch_async会导致后台队列的死锁?

时间:2022-05-15 20:59:54

I am creating a serial background queue like this:

我正在创建一个串行背景队列,如下所示:

@property (nonatomic, strong) dispatch_queue_t assetCreationQueue;

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

Then I enumerate ALAsset objects in the background like this:

然后我在后台列举了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];
                });
            }
        });
    }
}];

The assets array property is defined as:

资产数组属性定义为:

@property (nonatomic, strong) NSMutableArray *assets;

When I try dispatch_sync(dispatch_get_main_queue(), ^{ I only get ONE NSLog(@"AVURLAsset %@", avAsset); in the console and it indicates that dispatch_sync is causing a deadlock.

当我尝试dispatch_sync(dispatch_get_main_queue()、^ {我只得到一个NSLog(@“AVURLAsset % @ avAsset);在控制台中,它指示dispatch_sync正在导致死锁。

But how can I find out why? I don't see where. The assetCreationQueue is a background queue and I have to operate on the array only on the main queue.

但是我怎么才能知道为什么呢?我看不出在哪里。assetCreationQueue是一个后台队列,我只能对主队列上的数组进行操作。

EDIT: Here is a much more simplified test which also fails:

编辑:这里有一个更简单的测试,它也失败了:

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

So what I don't understand is: Why am I already on the main thread even though I call dispatch_async(weakSelf.assetCreationQueue. It can only lead to evil conclusion: The queue I create is NOT a background queue:

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

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

Why?

为什么?

1 个解决方案

#1


3  

(Not really an answer, but I want to provide enough information to move forward.)

(不是真正的答案,但我想提供足够的信息,让我们向前迈进。)

I've tried a very simple program, and this doesn't reproduce at all. I get many calls to "dispatch async on main queue" and no calls to "already main thread" (the opposite of what you're expecting). I'm running this on an iPhone 5. Complete code:

我尝试过一个非常简单的程序,但它根本不能复制。我收到许多调用“在主队列上分派异步”,而没有调用“已经主线程”(与您期望的相反)。我在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

Note that the use of weakSelf here is unnecessary and dangerous (but if this were the problem, you'd be crashing). You only need weakSelf if the block is going to be stored in a property of an object that self retains. It is not necessary or even desired for short-lived blocks like this. The way you've used it, if the owning object deallocated in the middle of the enumeration (which it can), weakSelf would become nil, weakSelf.assetCreationQueue would be nil, and dispatch_async() would crash. (But again, this is unlikely the cause of your issue, since it should crash.)

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

#1


3  

(Not really an answer, but I want to provide enough information to move forward.)

(不是真正的答案,但我想提供足够的信息,让我们向前迈进。)

I've tried a very simple program, and this doesn't reproduce at all. I get many calls to "dispatch async on main queue" and no calls to "already main thread" (the opposite of what you're expecting). I'm running this on an iPhone 5. Complete code:

我尝试过一个非常简单的程序,但它根本不能复制。我收到许多调用“在主队列上分派异步”,而没有调用“已经主线程”(与您期望的相反)。我在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

Note that the use of weakSelf here is unnecessary and dangerous (but if this were the problem, you'd be crashing). You only need weakSelf if the block is going to be stored in a property of an object that self retains. It is not necessary or even desired for short-lived blocks like this. The way you've used it, if the owning object deallocated in the middle of the enumeration (which it can), weakSelf would become nil, weakSelf.assetCreationQueue would be nil, and dispatch_async() would crash. (But again, this is unlikely the cause of your issue, since it should crash.)

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