27

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.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Can you describe what you're trying to achieve? Why does it matter for your particular purposes what thread it's executed on? – Christopher Pickslay Oct 31 '12 at 22:19
  • @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. – rmaddy Oct 31 '12 at 22:39
  • 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 Oct 31 '12 at 22:54
  • Have you thought about adding the queue as a method parameter, as in [addBoundaryTimeObserverForTimes:queue:usingBlock:](https://developer.apple.com/library/mac/documentation/AVFoundation/Reference/AVPlayer_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40009530-CH1-SW19)? – Christopher Pickslay Oct 31 '12 at 22:57
  • @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. – rmaddy Nov 01 '12 at 02:31
  • 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 – Arian Sharifian May 07 '13 at 03:50
  • @ArianSharifian It's deprecated in iOS 6 - see http://stackoverflow.com/questions/13237417/alternatives-to-dispatch-get-current-queue-for-completion-blocks-in-ios-6/ – rmaddy May 07 '13 at 04:27
  • @rmaddy sorry, you're right. It wasn't in documentation – Arian Sharifian May 07 '13 at 19:16
  • UIDocument is probably using the deprecated function. – Catfish_Man May 07 '13 at 22:13
  • @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). – Dima Jun 18 '14 at 07:02
  • 1
    @Dima No I didn't but my needs changed such that it was no longer an issue. – rmaddy Jun 18 '14 at 15:08

4 Answers4

13

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.

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
D.C.
  • 15,340
  • 19
  • 71
  • 102
  • 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 Oct 27 '12 at 16:06
  • +1 Create your own serial queue and run the method and the completion block on that queue. – Abizern Nov 04 '12 at 05:12
  • 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. – Catfish_Man May 07 '13 at 20:11
6

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • 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. – rmaddy Oct 27 '12 at 16:11
  • **"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? – TotoroTotoro Dec 09 '13 at 20:22
  • @BlackRider: https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html, under the "Queuing Tasks for Dispatch" heading. – jscs Dec 09 '13 at 20:28
  • @JoshCaswell: Thanks. So you're talking about the GCD dispatch queues. I was thinking about using `NSOperationQueue` instead. – TotoroTotoro Dec 09 '13 at 20:34
  • 3
    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. – jscs Dec 09 '13 at 20:38
  • 1
    Is there anyway to do this in Swift? – AnthonyMDev Feb 25 '16 at 00:53
  • the question indicates "I don't want to assume the main thread." so what is current thread is main thread? – hariszaman May 11 '17 at 14:46
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);
    }];
}];
Arian Sharifian
  • 1,295
  • 11
  • 14
  • 2
    [NSOperationQueue currentQueue] may return nil. "Calling this method from outside the context of a running operation typically results in nil being returned." – BB9z Apr 18 '16 at 04:27
0

I wanted to do some tasks on some queue and then execute a completion block as @rmaddy mentioned. I came across the Concurrency Programming Guide from Apple and implemented this (with dispatch_retain & dispatch_released commented out because I am using ARC) -- https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1

void average_async(int *data, size_t len, dispatch_queue_t queue, void (^block)(int))
{
// Retain the queue provided by the user to make
// sure it does not disappear before the completion
// block can be called.
//dispatch_retain(queue); // comment out if use ARC

// Do the work on user-provided queue
dispatch_async(queue, ^{
  int avg = average(data, len);
  dispatch_async(queue, ^{ block(avg);});

  // Release the user-provided queue when done
  //dispatch_release(queue); // comment out if use ARC
});
}
MobileDev
  • 3,750
  • 4
  • 32
  • 35
  • 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 May 11 '17 at 14:29