有没有什么方法可以确保GCD任务在没有串行队列的情况下完成?

时间:2013-07-16 08:23:58

标签: objective-c grand-central-dispatch

我正在使用GCD进行繁重的操作 - 图像处理等等 - 通常会同时运行3或4个任务。

其中一些任务比其他任务更快完成。如何确保以正确的原始顺序触发回调 - 而不使用串行队列

例如:

  • 任务一需要1秒
  • 任务2需要5秒
  • 任务3需要2秒

尽管计算时间不同,我如何确保最终的回调顺序为一,二,三?

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

修改

根据评论,即使剩余的任务正在处理,第一个通知应在任务一完成时立即发出。当任务三完成时,它应按住直到任务二完成,然后首先快速连续关闭两个和三个的通知。

我正在考虑推送和转移任务的某种可变阵列可以工作。有没有更清洁的方式?

6 个答案:

答案 0 :(得分:11)

每个完成块(除了第一个)都有两个依赖关系:繁重的工作和之前繁重工作的完成块。

使用NSOperationQueueNSBlockOperation而不是直接使用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任务提供。

相关问题