我正在使用GCD进行繁重的操作 - 图像处理等等 - 通常会同时运行3或4个任务。
其中一些任务比其他任务更快完成。如何确保以正确的原始顺序触发回调 - 而不使用串行队列?
例如:
尽管计算时间不同,我如何确保最终的回调顺序为一,二,三?
// self.queue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(self.queue, ^{
// Long-running code here of varying complexity
dispatch_async(dispatch_get_main_queue(), ^{
// Callback here
});
});
修改
根据评论,即使剩余的任务正在处理,第一个通知应在任务一完成时立即发出。当任务三完成时,它应按住直到任务二完成,然后首先快速连续关闭两个和三个的通知。
我正在考虑推送和转移任务的某种可变阵列可以工作。有没有更清洁的方式?
答案 0 :(得分:11)
每个完成块(除了第一个)都有两个依赖关系:繁重的工作和之前繁重工作的完成块。
使用NSOperationQueue
和NSBlockOperation
而不是直接使用GCD来满足您的要求会更加简单。 (NSOperationQueue
建立在GCD之上。)
您需要一个操作队列和对先前完成操作的引用:
@property (nonatomic, strong) NSOperationQueue *queue;
@property (nonatomic, strong) NSOperation *priorCompletionOperation;
将queue
初始化为NSOperationQueue
。保留priorCompletionOperation
nil,直到你找到第一份工作。
然后,只需在将操作提交到队列之前设置依赖项:
NSBlockOperation *heavyLifting = [NSBlockOperation blockOperationWithBlock:^{
// long-running code here of varying complexity
}];
NSBlockOperation *completion = [NSBlockOperation blockOperationWithBlock:^{
// Callback here
}];
[completion addDependency:heavyLifting];
if (self.priorCompletionOperation) {
[completion addDependency:self.priorCompletionOperation];
}
[self.queue addOperation:heavyLifting];
[[NSOperationQueue mainQueue] addOperation:completion];
self.priorCompletionOperation = completion;
请注意,您应该确保此作业排队代码一次只能从一个线程运行。如果您只从主线程(或主队列)中排队将自动发生的作业。
答案 1 :(得分:5)
我很佩服rob mayoff的解决方案。然而,还有一种简单的方法可以在GCD中实现这一目标;)
这是一个完整的样本:
#import <Foundation/Foundation.h>
#include <dispatch/dispatch.h>
typedef void (^completion_block_t)(id result);
typedef void (^operation_t)(completion_block_t completionHandler);
static void enqueueOperation(dispatch_queue_t process_queue,
dispatch_queue_t sync_queue,
operation_t operation)
{
__block id blockResult = nil;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(sync_queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"%@", blockResult);
});
dispatch_async(process_queue, ^{
operation(^(id result) {
blockResult = result;
dispatch_semaphore_signal(sem);
// release semaphore
});
});
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
NSLog(@"Start");
dispatch_queue_t process_queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t sync_queue = dispatch_queue_create("sync_queue", NULL);
enqueueOperation(process_queue, sync_queue, ^(completion_block_t completionHandler) {
sleep(1);
completionHandler(@"#1");
});
enqueueOperation(process_queue, sync_queue, ^(completion_block_t completionHandler) {
sleep(5);
completionHandler(@"#2");
});
enqueueOperation(process_queue, sync_queue, ^(completion_block_t completionHandler) {
sleep(2);
completionHandler(@"#3");
});
sleep(6);
NSLog(@"Finished");
}
return 0;
}
2013-07-16 14:16:53.461 test[14140:303] Start
2013-07-16 14:16:54.466 test[14140:1a03] #1
2013-07-16 14:16:58.467 test[14140:1a03] #2
2013-07-16 14:16:58.468 test[14140:1a03] #3
2013-07-16 14:16:59.467 test[14140:303] Finished
(顺便说一句,这是我的网络库的一部分,用于并行处理multipart / x-replace消息)
答案 2 :(得分:3)
所以最终的解决方案很简单。
我喜欢NSOperation的简单性,但我不喜欢必须保持对前一个迭代队列的引用,或者在如此紧密的循环中创建和销毁NSOperations。
我最终选择了GCD / Semaphore模型的变体:
// self.serialQueue = dispatch_queue_create("com.example.queue", NULL);
// self.conQueue = dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_CONCURRENT);
// Create a variable that both blocks can access, so we can pass data between them
__block id *message = nil;
// Create semaphore which will be used to lock the serial/callback queue
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// This is effectively the 'callback' that gets fired when the semaphore is released
dispatch_async(self.serialQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (message) {
NSLog(@"Callback message: %@", message);
}
});
// Do the heavy lifting, then release the semaphore which allows the callback to fire
dispatch_async(self.conQueue, ^{
// ... Heavy lifting here
message = [NSString stringWithFormat:@"Success!"];
dispatch_semaphore_signal(semaphore);
});
答案 3 :(得分:1)
如何确保以正确的原始顺序触发回调 - 而不使用串行队列?
你没有 - 回调告诉你任务何时完成,如果他们按照与排队顺序不同的顺序完成,那么回调将按照他们的完成顺序触发。
然而,区别在于您处理完成通知的方式。您可以像处理线程问题一样对待它,在三个工作线程上执行join
。等待第一个,然后是第二个,然后第三个完成。为此,您可以使用信号量或互斥量(甚至是原子计数器),每次完成时都会引发它。
这样,您的处理程序代码并不真正关心完成顺序;它只是等待,直到每个任务按顺序完成。如果任务已经完成,则不需要等待。在等待之间,您也可以随时做其他事情。
答案 4 :(得分:1)
正如评论中所提到的,最简单的解决方案是仅在最终任务完成时运行回调。然后,您可以按顺序调用所有任务的回调代码。即如果需要,将对象添加到数组中,然后当回调中的增量计数器达到数组中的对象数时,执行回调代码。
如果回调是独立于对象的,那么您可以在完成后使用计数器并运行代码。请记住在主线程中运行任何计数器操作或使用'@sync()'指令来避免竞争条件。
dispatch_async(dispatch_get_main_queue(), ^(void){ /* code*/ });
编辑:在完成处理程序中使用相同的数组技术,将对象的标志设置为准备发送。然后尽可能遍历数组并发送所有准备好的对象。否则停止并等待下一个完成处理程序调用。您可以使用计数器跟踪位置或从阵列中删除项目,但一定要在主线程或同步块上进行。
@interface MyImage : NSImage
@property (assign) BOOL ready;
@end
@implementation MyImage
@synthesize ready;
- (void)send {
//Send image, make sure it's threaded send, NSConnection should be okay
}
@end
NSArray *imagesNeededToSend = [NSMutableArray arrayWithObjects:image1, image2, image3, nil];
dispatch_async(self.queue, ^{
// Long-running code here of varying complexity
dispatch_async(dispatch_get_main_queue(), ^{
self.ready = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){ [self sendImages] });
});
});
...
- (void)sendImages {
MyImage *firstImage = [imagesNeededToSend firstObject]; //Xcode Beta, but you should have a a category for firstObject, very useful.
if (firstImage.ready) {
[firstImage send];
[imagesNeededToSend removeObjectAtIndex:0];
[self sendImages];
}
}
答案 5 :(得分:1)
您有两种不同的东西,一组并发的图像处理和一系列上传处理。您可以创建NSOperation
来处理上载处理并设置它们之间的依赖关系,以便它们必须按顺序运行。操作也会在它们启动后等待(信号量),直到它们收到要上载的数据。该数据将由并发的GCD任务提供。