dispatch_async and calling a completion handler on the original queue

25,393

Solution 1

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.

Solution 2

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.

Solution 3

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);
    }];
}];
Share:
25,393

Related videos on Youtube

rmaddy
Author by

rmaddy

I am an independent iOS app developer producing and selling my own apps. I started with BASIC in 1979 playing with TRS Model I computers at Radio Shack stores. We sure had a lot of fun with 4k of RAM.

Updated on February 07, 2020

Comments

  • rmaddy
    rmaddy about 4 years

    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.

    • Christopher Pickslay
      Christopher Pickslay over 11 years
      Can you describe what you're trying to achieve? Why does it matter for your particular purposes what thread it's executed on?
    • rmaddy
      rmaddy over 11 years
      @ChristopherPickslay someMethod might be called in some background thread. I want it such that the completion block is called on that same thread, not the main thread or some other arbitrary background thread.
    • Christopher Pickslay
      Christopher Pickslay over 11 years
      I understand that. The question is why. Is there some technical reason it needs to be invoked on a specific thread? I'm just thinking there might be a different design that would help.
    • Christopher Pickslay
      Christopher Pickslay over 11 years
      Have you thought about adding the queue as a method parameter, as in addBoundaryTimeObserverForTimes:queue:usingBlock:?
    • rmaddy
      rmaddy over 11 years
      @ChristopherPickslay Thanks. I'll take a look. Much of my question came about from discussions about the deprecation of dispatch_get_current_queue and wondering how methods like UIDocument saveToURL:forSaveOperation:completionHandler: achieve what they claim.
    • Arian Sharifian
      Arian Sharifian almost 11 years
      Why can't you use 'dispatch_get_current_queue'? According to doc: Returns the queue on which the currently executing block is running. and as far as I see you are getting it inside the block
    • rmaddy
      rmaddy almost 11 years
      @ArianSharifian It's deprecated in iOS 6 - see stackoverflow.com/questions/13237417/…
    • Arian Sharifian
      Arian Sharifian almost 11 years
      @rmaddy sorry, you're right. It wasn't in documentation
    • Catfish_Man
      Catfish_Man almost 11 years
      UIDocument is probably using the deprecated function.
    • Dima
      Dima almost 10 years
      @rmaddy did you ever figure out a straightforward solution to this? I'm in a similar situation right now where a method may be invoked from either the main queue or a background queue, then its body will run on a background queue, and its completion block should ideally be called from the original calling queue (which again, may or may not be the main queue).
    • rmaddy
      rmaddy almost 10 years
      @Dima No I didn't but my needs changed such that it was no longer an issue.
  • rmaddy
    rmaddy over 11 years
    Look at the docs for UIDocument saveToURL:forSaveOperation:completionHandler:. The description of the completion handler state This block is invoked on the calling queue.. This is what I wish to achieve.
  • rmaddy
    rmaddy over 11 years
    This is an interesting idea. But this has a serious downside. For every possible block signature I might want to use in this manner, I would have to add a corresponding category method.
  • Abizern
    Abizern over 11 years
    +1 Create your own serial queue and run the method and the completion block on that queue.
  • Catfish_Man
    Catfish_Man almost 11 years
    I would argue that this is a bug in UIDocument. It shouldn't be assuming that the originating queue has the behaviors that it wants.
  • TotoroTotoro
    TotoroTotoro over 10 years
    "aside from the main queue, none of them are guaranteed to be running on any particular thread" -- could you please point me to the place in the Apple docs that says this?
  • jscs
    jscs over 10 years
    @BlackRider: developer.apple.com/library/mac/documentation/Performance/…, under the "Queuing Tasks for Dispatch" heading.
  • TotoroTotoro
    TotoroTotoro over 10 years
    @JoshCaswell: Thanks. So you're talking about the GCD dispatch queues. I was thinking about using NSOperationQueue instead.
  • jscs
    jscs over 10 years
    Sure thing, @BlackRider. An NSOperationQueue will also have its own private thread, and I'm pretty sure it's built on top of GCD/libdispatch.
  • AnthonyMDev
    AnthonyMDev about 8 years
    Is there anyway to do this in Swift?
  • BB9z
    BB9z about 8 years
    [NSOperationQueue currentQueue] may return nil. "Calling this method from outside the context of a running operation typically results in nil being returned."
  • hariszaman
    hariszaman almost 7 years
    you quoted the answer in a wrong way :) the first async is on the global queue lol. After the work is done on that queue the block is executed on the queue passed in
  • hariszaman
    hariszaman almost 7 years
    the question indicates "I don't want to assume the main thread." so what is current thread is main thread?