GCD串行队列似乎不是串行执行的

我有一个方法,有时可以在我的代码中调用。 下面是一个非常基本的示例,因为代码处理iphone照片库中的图像和文件,并在完成该方法时标记它们已经处理过。

@property (nonatomic, assign) dispatch_queue_t serialQueue; .... -(void)processImages { dispatch_async(self.serialQueue, ^{ //block to process images NSLog(@"In processImages"); .... NSLog(@"Done with processImages"); }); } 

我认为每次调用此方法时我都会得到以下输出…“在processImages中”“使用processImages完成”“在processImages中”“完成了processImages”等…

但我总是得到

“在processImages”“在processImages”“完成与processImages”“完成与processImages”等…

我以为串口队列会等到第一个块完成,然后开始。 对我来说,似乎它正在启动方法,然后它再次被调用并在第一个调用完成之前启动,创建通常不会被处理的图像副本,因为如果它真的按顺序执行,则该方法将知道它们已经处理完毕。 也许我对串行队列的理解并不具体。 任何输入? 谢谢。

编辑:更多上下文,这是在块中发生的事情…这可能导致问题???

 @property (nonatomic, assign) dispatch_queue_t serialQueue; .... -(void)processImages { dispatch_async(self.serialQueue, ^{ //library is a reference to ALAssetsLibrary object [library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) { .... //Process the photos here }]; failureBlock:^(NSError *error) { NSLog(@"Error loading images from library"); }]; }); } -(id)init { self = [super init]; if(self) { _serialQueue = dispatch_queue_create("com.image.queue",NULL); } return self; } 

这个对象只创建一次,据我所知,永远不能再根据我的代码创建…我会运行测试以确保。

更新2:我认为发生了什么,如果您同意/不同意,请对此发表评论….

显然我的主要问题是,似乎这个代码块正在同时执行,创建重复的条目(导入相同的照片两次),如果它是串行运行通常不会这样做。 处理照片时,会对其应用“脏”位,以确保下次调用该方法时它会跳过此图像,但这不会发生,并且某些图像会被处理两次。 这可能是因为我使用enumerategroupswithtypes枚举第二个队列中的对象:在该serialQueue中?

  1. 调用processImages
  2. enumerateObjects
  3. 立即从enumerateObjects返回,因为它本身是异步的
  4. 结束对processImages的调用

虽然由于enumerategroups可能仍在运行,但是队列可能已经完成,因为它在枚举组完成工作之前到达块的末尾,所以processImages并没有真正完成。 这对我来说似乎有可能吗?

串行队列绝对会连续执行。 但是,它们不能保证在同一个线程上执行。

假设您使用相同的串行队列,问题是NSLog不能保证在从不同线程同时调用时以正确的顺序输出结果。

这是一个例子:

  1. SQ在线程X上运行,发送“In processImages”
  2. 日志打印“在proc”
  3. 线程X上的SQ发送“使用processImages完成”
  4. SQ在线程Y上运行,发送“In processImages”
  5. 日志打印“essImages \ n”

5.之后,NSLog不一定知道要打印哪个,3或4。

如果您绝对需要按时间顺序记录,则需要一个专用的队列进行记录。 在实践中,我只使用主队列没有问题:

 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"whatever"); }); 

如果所有NSlog调用都在同一队列中,则不应该出现此问题。

如果问题是“串行队列可以异步执行任务吗?” 然后答案是否定的。 如果您认为可以,则应确保所有任务确实在同一队列上执行。 您可以在块中添加以下行并比较输出:

 dispatch_async(self.serialQueue, ^{ NSLog(@"current queue:%p current thread:%@",dispatch_get_current_queue(),[NSThread currentThread]); 

确保在队列中执行的块中编写NSLog,而不是在enumerateGroupsWithTypes中编写:usingBlock:failureBlock:您也可以尝试创建这样的队列

 dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL); 

但我认为这不会改变任何事情

编辑:顺便说一句,方法

 enumerateGroupsWithTypes:usingBlock:failureBlock: 

是异步的,为什么要在另一个队列上调用它?

更新2:我可以建议这样的事情:

 dispatch_async(queue, ^{ NSLog(@"queue"); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER, *pmutex = &mutex; pthread_mutex_lock(pmutex); ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) { NSLog(@"block"); if (group) { [groups addObject:group]; } else { [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]; dispatch_async(dispatch_get_current_queue(), ^{ pthread_mutex_unlock(pmutex); }); } NSLog(@"block end"); }; [assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:listGroupBlock failureBlock:failureBlock]; pthread_mutex_lock(pmutex); pthread_mutex_unlock(pmutex); pthread_mutex_destroy(pmutex); NSLog(@"queue end"); }); 

enumerateGroupsWithTypes:usingBlock:failureBlock:在另一个线程上异步工作,并在完成后调用传入的块(在我认为的主线程上)。 从另一个角度来看,如果它在方法调用完成时同步完成所有同步,则它可以只返回组的枚举器对象,例如,用于更简单的API。

从文档:

此方法是异步的。 枚举组时,可能会要求用户确认应用程序对数据的访问权限; 但是,该方法立即返回。 您应该使用enumerationBlock中的资源执行所需的任何工作。

我不确定你为什么要通过使用串行队列来完成,但如果你只是想阻止同时访问,那么你可以在某处添加一个变量来跟踪我们当前是否枚举,并检查首先,如果您不必担心同步问题。 (如果你这样做,或许你应该考虑使用一个GCD组,但这可能是过度杀戮的。)

我遇到了这样的问题,我的答案就是要意识到来自序列化队列上的方法的异步调用会转到另一个队列进行处理 – 一个未序列化的队列。

因此,您必须使用显式dispatch_async(serializedQueue, ^{})将所有调用包装在main方法中,以确保所有操作都以正确的顺序完成…

您可能有多个对象,每个对象都有自己的串行队列。 分派到任何单个串行队列的任务是串行执行的,但分派到不同串行队列的任务绝对是交错的。

另一个简单的错误是创建不是串行队列,而是创建并发队列……