Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dispatch_async and calling a completion handler on the original queue

I've seen some related questions but none seem to answer this case. I want to write a method that will do some work in the background. I need this method to call a completion callback on the same thread / queue used for the original method call.

- (void)someMethod:(void (^)(BOOL result))completionHandler {
    dispatch_queue_t current_queue = // ???

    // some setup code here
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        BOOL ok = // some result

        // do some long running processing here

        dispatch_async(current_queue, ^{
            completionHandler(ok);
        });
    });

What magic incantation is needed here so the completion handler is called on the same queue or thread as the call to sameMethod? I don't want to assume the main thread. And of course dispatch_get_current_queue is not to be used.

like image 242
rmaddy Avatar asked Oct 27 '12 07:10

rmaddy


3 Answers

If you look through the Apple docs, there appear to be two patterns.

If it is assumed that the completion handler is to be run on the main thread, then no queue needs to be provided. An example would be UIView's animations methods:

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

Otherwise, the API usually asks the caller to provide a queue:

[foo doSomethingWithCompletion:completion targetQueue:yourQueue];

My suggestion is to follow this pattern. If it is unclear which queue the completion handler should be called, the caller should supply it explicitly as a parameter.

like image 130
D.C. Avatar answered Oct 14 '22 01:10

D.C.


You can't really use queues for this because, aside from the main queue, none of them are guaranteed to be running on any particular thread. Instead, you will have to get the thread and execute your block directly there.

Adapting from Mike Ash's Block Additions:

// The last public superclass of Blocks is NSObject
@implementation NSObject (rmaddy_CompletionHandler)

- (void)rmaddy_callBlockWithBOOL: (NSNumber *)b
{
    BOOL ok = [b boolValue];
    void (^completionHandler)(BOOL result) = (id)self;
    completionHandler(ok);
}

@end

- (void)someMethod:(void (^)(BOOL result))completionHandler {
    NSThread * origThread = [NSThread currentThread];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        BOOL ok = // some result

        // do some long running processing here

        // Check that there was not a nil handler passed.
        if( completionHandler ){
            // This assumes ARC. If no ARC, copy and autorelease the Block.
            [completionHandler performSelector:@selector(rmaddy_callBlockWithBOOL:)
                                      onThread:origThread
                                    withObject:@(ok)    // or [NSNumber numberWithBool:ok]
                                 waitUntilDone:NO];
        }
        });
    });

Although you're not using dispatch_async(), this is still asynchronous with respect to the rest of your program, because it's contained within the original dispatched task block, and waitUntilDone:NO also makes it asynchronous with respect to that.

like image 6
jscs Avatar answered Oct 14 '22 03:10

jscs


not sure if this will solve the problem, but how about using NSOperations instead of GCD?:

- (void)someMethod:(void (^)(BOOL result))completionHandler {
NSOperationQueue *current_queue = [NSOperationQueue currentQueue];

// some setup code here
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperationWithBlock:^{
    BOOL ok = YES;// some result

    // do some long running processing here
    [current_queue addOperationWithBlock:^{
        completionHandler(ok);
    }];
}];
like image 3
Arian Sharifian Avatar answered Oct 14 '22 02:10

Arian Sharifian