Метод отправки Objective-C с блоком, который будет выполняться в потоке * caller * - PullRequest
2 голосов
/ 09 сентября 2011

Я пишу класс черного ящика, который выполняет тяжелую обработку в фоновом режиме, используя 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().См., Например, Вызов метода модели с блоком, который будет выполняться в главном потоке .

Однако в этом обычном сценарии предполагается, что клиент вызывает из основного потока.Я хотел бы быть более общим и вызвать блок продолжения в потоке вызывающего, который может быть или не быть основным потоком.Это позволило бы, например, хорошо работать с многопоточными клиентами Core Data, где NSManagedObjectContext является локальным для потока.Есть ли хороший шаблон для этого?

Используя –[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;

Ответы [ 4 ]

4 голосов
/ 09 сентября 2011

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 и выпустить его позже. Я думаю, что нет.

Надеюсь, что это поможет вам!

РЕДАКТИРОВАТЬ : добавлено сохранение / освобождение

3 голосов
/ 18 декабря 2011

Плохо пытаться угадать намерение вызывающей стороны относительно того, где должен выполняться блок обработчика завершения.Вы можете ошибиться, и произойдут плохие вещи.

Очередь, возвращаемая dispatch_get_current_queue(), может использоваться вызывающей стороной способами, которые вы не ожидаете и не можете предвидеть;возможно, они вызовут dispatch_suspend() к этому времени, когда вы поставите в очередь свой обработчик завершения.Вы также не можете предположить, что можете отправить performSelector: на возвращаемое значение [NSThread currentThread] в какой-то произвольный момент в будущем;что если этот поток завершился к тому времени, когда вы поставили в очередь обработчик завершения?Что если этот поток не имеет цикла выполнения?В обоих случаях ваш обработчик завершения никогда не запускается.

Это также противоречит поведению многих методов / функций Apple, которые принимают блок обработчика завершения.Как правило, клиент предоставляет очередь напрямую, или обработчик завершения выполняется в произвольном потоке, и в этом случае клиент несет ответственность за переход в нужную очередь или поток при необходимости.

1 голос
/ 09 сентября 2011

Вы можете просто добавить другой параметр в ваш метод -processHeavyStuff:thenDo:, указав очередь, в которой вы хотите запустить блок. Этот подход гораздо более гибкий и используется в NSNotificationCenter -addObserverForName:object:queue:usingBlock: (хотя он использует NSOperationQueues, но принцип тот же). Только не забывайте, что вы должны сохранить и освободить очередь, которую вы проходите.

0 голосов
/ 14 апреля 2012

Вот как я справлюсь с этим.Он гибкий, но не очень элегантный.

Поскольку вопрос до сих пор остается без ответа, может быть, вы все еще ищете ответ?Если нет, не могли бы вы представить, что работает лучше для вас?

// 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;
...