dispatch_async and calling a completion handler on the original queue
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);
}];
}];
Related videos on Youtube
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, 2020Comments
-
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 coursedispatch_get_current_queue
is not to be used.-
Christopher Pickslay over 11 yearsCan you describe what you're trying to achieve? Why does it matter for your particular purposes what thread it's executed on?
-
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 over 11 yearsI 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 over 11 yearsHave you thought about adding the queue as a method parameter, as in addBoundaryTimeObserverForTimes:queue:usingBlock:?
-
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 likeUIDocument saveToURL:forSaveOperation:completionHandler:
achieve what they claim. -
Arian Sharifian almost 11 yearsWhy 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 almost 11 years@ArianSharifian It's deprecated in iOS 6 - see stackoverflow.com/questions/13237417/…
-
Arian Sharifian almost 11 years@rmaddy sorry, you're right. It wasn't in documentation
-
Catfish_Man almost 11 yearsUIDocument is probably using the deprecated function.
-
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 almost 10 years@Dima No I didn't but my needs changed such that it was no longer an issue.
-
-
rmaddy over 11 yearsLook 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 over 11 yearsThis 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 over 11 years+1 Create your own serial queue and run the method and the completion block on that queue.
-
Catfish_Man almost 11 yearsI 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 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 over 10 years@BlackRider: developer.apple.com/library/mac/documentation/Performance/…, under the "Queuing Tasks for Dispatch" heading.
-
TotoroTotoro over 10 years@JoshCaswell: Thanks. So you're talking about the GCD dispatch queues. I was thinking about using
NSOperationQueue
instead. -
jscs over 10 yearsSure 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 about 8 yearsIs there anyway to do this in Swift?
-
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 almost 7 yearsyou 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 almost 7 yearsthe question indicates "I don't want to assume the main thread." so what is current thread is main thread?