具有块的Objective-C调度方法将在*调用者*线程上运行

时间:2011-09-09 12:06:39

标签: objective-c multithreading grand-central-dispatch objective-c-blocks

我写了一个黑盒子类,使用Grand Central Dispatch在后台进行大量处理。我打算提供一个延续式API,如:

- (void) processHeavyStuff:(id) someParam thenDo:(ContinuationBlock)followup;

客户可以像这样打电话:

[myBlackBox processHeavyStuff:heavyOne thenDo: ^(Dalek* result){
    [self updateDisplayWithNewDalek:result];
}];

通常做的是processHeavyStuff:thenDo:实现使用dispatch_get_main_queue()在主线程上调用其连续块。有关示例,请参阅Invoke model method with block that will run on the main thread

然而,这种常见情况假设客户端正在从主线程调用。我想更通用,并在调用者的线程上调用continuation块,这可能是也可能不是主线程。例如,这将允许与NSManagedObjectContext是线程本地的Core Data线程客户端一起使用。对此有好的模式吗?

使用–[NSObject performSelector:onThread:withObject:waitUntilDone:],我可以看到我可以定义辅助方法:

- (void) callContinuation:(ContinuationBlockWithNoArgument) followup
{
    followup();
}

然后在调用者的线程上执行该选择器:

- (void) processHeavyStuff:(id) someParam thenDo:(ContinuationBlock)followup
{
    NSSthread *callerThread = [NSThread currentThread];

    dispatch_async(self.backgroundQueue, ^ {
        Dalek *newDalek = [self actuallyDoTheHeavyProcessing:someParam];

        [self performSelector:@selector(callContinuation:) onThread:callerThread
            withObject: ^{
                followup(newDalek);
            }
            waitUntilDone:NO];
    });
}

我想这可行,我会尝试一下。但是有什么不那么做作的吗?也许是块performSelector:onThread:的版本?

PS:为了清楚起见,我将所有内存管理调用从上述代码段中删除。例如,followup块是基于堆栈的,必须复制到堆中才能在另一个线程上使用它...

编辑:我发现Mike Ash使用了非常类似的方法:

void RunOnThread(NSThread *thread, BOOL wait, BasicBlock block)
{
    [[[block copy] autorelease] performSelector: @selector(my_callBlock) onThread: thread withObject: nil waitUntilDone: wait];
}

my_callBlock上的类别中定义了NSObject

@implementation NSObject (BlocksAdditions)
- (void)my_callBlock
{
    void (^block)(void) = (id)self;
    block();
}
@end;

3 个答案:

答案 0 :(得分:4)

dispatch_get_current_queue()返回当前队列,该队列是在方法开头调用时的调用者队列。将您的代码更改为:

- (void)processHeavyStuff:(id)someParam thenDo:(ContinuationBlock)followup {
    dispatch_queue_t callerQueue = dispatch_get_current_queue();
    dispatch_retain(callerQueue);

    dispatch_async(self.backgroundQueue, ^ {
        Dalek *newDalek = [self actuallyDoTheHeavyProcessing:someParam];

        dispatch_async(callerQueue, ^{
            followUp(dalek);
            dispatch_release(callerQueue);
        });
    });
}

我不太确定的一件事是你是否需要保留callerQueue并在之后释放它。我想你没有。

希望能帮到你!

编辑:添加了保留/释放

答案 1 :(得分:1)

您可以简单地在-processHeavyStuff:thenDo:方法中添加另一个参数,指定您希望块运行的队列。这种方法更灵活,在NSNotificationCenter的{​​{1}}中使用(尽管它使用-addObserverForName:object:queue:usingBlock:,但原理是相同的)。只是不要忘记你应该保留并释放你传入的队列。

答案 2 :(得分:0)

这是我如何处理它。它很灵活,但不是很优雅。

由于问题仍然没有答案,也许你还在寻找答案?如果没有,你能否提供最适合你的东西?

// Continuation block is executed on an arbitrary thread.
// Caller can change context to specific queue/thread in
// the continuation block if necessary.
- (void) processHeavyStuff:(id) someParam thenDo:(ContinuationBlock)followup;

// Continuation block is executed on the given GCD queue
- (void) processHeavyStuff:(id) someParam thenOnQueue:(dispatch_queue_t)queue do:(ContinuationBlock)followup;

// Continuation block is executed on the given thread
- (void) processHeavyStuff:(id) someParam thenOnThread:(NSThread*)thread do:(ContinuationBlock)followup;

// Continuation block is executed on *this* thread
- (void) processHeavyStuff:(id) someParam thenDoOnThisThread:(ContinuationBlock)followup;

// Continuation block is executed on main thread
- (void) processHeavyStuff:(id) someParam thenDoOnMainThread:(ContinuationBlock)followup;