How to get the dispatch queue for the current thread's runloop?

  • I'm really used to using -performSelector:withObject:afterDelay: to make something happen later. But I'd much rather use a block than a target/action. I can't find any API for this, however. Am I missing something? What I want is basically like
    PerformBlockAfterDelay(^{ …code here…}, 5.0);

    It looks like I should just call dispatch_async, but I'm unsure which dispatch queue to use. dispatch_get_main_queue only works for the main thread. Can I use dispatch_get_current_queue? I'm unsure of what this does when called on a background thread. The API docs say "If the call is made from any other thread, this function returns the default concurrent queue" … is that a queue associated with the thread's runloop, that's guaranteed to execute tasks on that thread?

    —Jens
  • On 2012-01-27, at 2:14 PM, Jens Alfke wrote:

    > I'm really used to using -performSelector:withObject:afterDelay: to make something happen later. But I'd much rather use a block than a target/action. I can't find any API for this, however. Am I missing something? What I want is basically like
    > PerformBlockAfterDelay(^{ …code here…}, 5.0);
    >
    > It looks like I should just call dispatch_async, but I'm unsure which dispatch queue to use. dispatch_get_main_queue only works for the main thread. Can I use dispatch_get_current_queue? I'm unsure of what this does when called on a background thread. The API docs say "If the call is made from any other thread, this function returns the default concurrent queue" … is that a queue associated with the thread's runloop, that's guaranteed to execute tasks on that thread?

    I just use a little category on NSObject that I think I pulled from somewhere—but I can't remember where from:

    #pragma mark -
    #pragma mark Delayed block execution

    - (void) performBlock:(void (^)(void)) block afterDelay:(NSTimeInterval) delay {
        block = [block copy];

        [self performSelector:@selector(fireBlockAfterDelay:)
                  withObject:block
                  afterDelay:delay];
    }

    - (void)fireBlockAfterDelay:(void (^)(void))block {
        block();
    }

    This is pretty old code (it relies on ARC, though), so there might be a better way to do it these days.

    —Mt.
  • This works:
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{});

    On 2012-01-27, at 3:14 PM, Jens Alfke wrote:

    > I'm really used to using -performSelector:withObject:afterDelay: to make something happen later. But I'd much rather use a block than a target/action. I can't find any API for this, however. Am I missing something? What I want is basically like
    > PerformBlockAfterDelay(^{ …code here…}, 5.0);
    >
    > It looks like I should just call dispatch_async, but I'm unsure which dispatch queue to use. dispatch_get_main_queue only works for the main thread. Can I use dispatch_get_current_queue? I'm unsure of what this does when called on a background thread. The API docs say "If the call is made from any other thread, this function returns the default concurrent queue" … is that a queue associated with the thread's runloop, that's guaranteed to execute tasks on that thread?
    >
    > —Jens
  • On 1/27/12 12:14 PM, Jens Alfke wrote:
    > I'm really used to using -performSelector:withObject:afterDelay: to
    > make something happen later. But I'd much rather use a block than a
    > target/action. I can't find any API for this, however. Am I missing
    > something? What I want is basically like PerformBlockAfterDelay(^{
    > …code here…}, 5.0);

    Take a look dispatch_after().

    The main downside I have found to using this is that there is no
    (direct) analog to cancelPerformSelector.  Most the idioms I have seen
    involve having the block check a variable (i.e. "BOOL stop") to
    determine whether they should continue executing.

    An alternative technique is a category on NSObject that has
    perform:/cancelPerform: methods accepting block arguments but using
    performSelector: internally to fire the block (or cancel firing).  Mike
    Ash has a blog post that touches on this approach
    (http://www.mikeash.com/pyblog/friday-qa-2009-08-14-practical-blocks.html).

    --
    Conrad Shultz

    Synthetiq Solutions
    www.synthetiqsolutions.com
  • On Jan 27, 2012, at 2:23 PM, Dave Fernandes wrote:

    > dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{});

    I want the block to run on the thread that's making this call. The global queue's not going to do that.

    —Jens
  • > It looks like I should just call dispatch_async, but I'm unsure which dispatch queue to use. dispatch_get_main_queue only works for the main thread. Can I use dispatch_get_current_queue? I'm unsure of what this does when called on a background thread. The API docs say "If the call is made from any other thread, this function returns the default concurrent queue" … is that a queue associated with the thread's runloop, that's guaranteed to execute tasks on that thread?

    Where do they say that?  That's surely wrong.  The man page says that in that case it returns the default-priority global [serial] queue.  http://developer.apple.com/library/mac/ipad/#documentation/Darwin/Reference
    /ManPages/man3/dispatch_get_main_queue.3.html
  • On Fri, Jan 27, 2012 at 12:14 PM, Jens Alfke <jens...> wrote:
    > I'm really used to using -performSelector:withObject:afterDelay: to make something happen later. But I'd much rather use a block than a target/action. I can't find any API for this, however. Am I missing something? What I want is basically like
    >        PerformBlockAfterDelay(^{ …code here…}, 5.0);
    >
    > It looks like I should just call dispatch_async, but I'm unsure which dispatch queue to use. dispatch_get_main_queue only works for the main thread. Can I use dispatch_get_current_queue? I'm unsure of what this does when called on a background thread. The API docs say "If the call is made from any other thread, this function returns the default concurrent queue" … is that a queue associated with the thread's runloop, that's guaranteed to execute tasks on that thread?

    My understanding is that you should treat the thread pool owned by a
    dispatch queue as private. That means you probably shouldn't be
    running an NSRunLoop on a dispatch worker thread.

    If you need a thread (including cases where you need to run a
    runloop), spawn a thread.

    --Kyle Sluder
  • On Jan 27, 2012, at 2:50 PM, Wade Tregaskis wrote:

    >> It looks like I should just call dispatch_async, but I'm unsure which dispatch queue to use. dispatch_get_main_queue only works for the main thread. Can I use dispatch_get_current_queue? I'm unsure of what this does when called on a background thread. The API docs say "If the call is made from any other thread, this function returns the default concurrent queue" … is that a queue associated with the thread's runloop, that's guaranteed to execute tasks on that thread?
    >
    > Where do they say that?  That's surely wrong.

    Um, in the API docs for dispatch_get_current_queue.

    file:///Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.AppleiOS5_0.iOSLibrary.docset/Contents/Resources/Documents/index.html#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html%23//apple_ref/c/func/dispatch_get_current_queue

    —Jens
  • On Jan 27, 2012, at 2:52 PM, Kyle Sluder wrote:

    > My understanding is that you should treat the thread pool owned by a
    > dispatch queue as private. That means you probably shouldn't be
    > running an NSRunLoop on a dispatch worker thread.
    > If you need a thread (including cases where you need to run a
    > runloop), spawn a thread.

    I think you've got it backwards. You're assuming I've got a dispatch queue and want to know the thread. But what I've actually got is a thread and I want a dispatch queue that will run on that thread (in synchrony with the runloop.)

    I don't know why this is so hard to explain! I just want a block-oriented alternative to the ubiquitous perform-selector-after-delay. It seems weird that there isn't one yet, since blocks have been available in two major OS releases so far.

    —Jens
  • On Fri, Jan 27, 2012 at 3:52 PM, Jens Alfke <jens...> wrote:
    > I think you've got it backwards. You're assuming I've got a dispatch queue
    > and want to know the thread. But what I've actually got is a thread and I
    > want a dispatch queue that will run on that thread (in synchrony with the
    > runloop.)

    This doesn't make sense. Runloops don't drive dispatch queues, except
    for the main thread's runloop which runs the main queue. (If anyone
    has proof to the contrary, I would very much welcome it.)

    --Kyle Sluder
  • Hi Jens,

    My understanding is that dispatch queues are tied to threads that are
    managed by the system and are separate from run loops. It's therefore
    non-sensical to ask for a runloop's queue.

    Regardless though, I think a better solution for you is a category on
    NSTimer. I use something like the following:

    ===================@implementation NSTimer (BlockTimersYay)
    + (NSTimer *)scheduledTimerWithTimeInterval:
    (NSTimeInterval)timeInterval repeats: (BOOL)repeats block: (void
    (^)(void))block
    {
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:
    timeInterval target: self selector: @selector(fireBlockTimer:)
    userInfo: [[block copy] autorelease] repeats: repeats];
        [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSRunLoopCommonModes];
        return timer;
    }
    + (void)fireBlockTimer: (NSTimer *)blockTimer
    {
        ((void (^)(void))[blockTimer userInfo])();
    }
    @end
    ===================
    Note that this is slightly different than NSTimer in that the timer is
    added to the common modes rather than the default mode.
  • On Jan 27, 2012, at 2:14 PM, Jens Alfke wrote:

    > I'm really used to using -performSelector:withObject:afterDelay: to make something happen later. But I'd much rather use a block than a target/action. I can't find any API for this, however. Am I missing something? What I want is basically like
    > PerformBlockAfterDelay(^{ …code here…}, 5.0);
    >
    > It looks like I should just call dispatch_async, but I'm unsure which dispatch queue to use. dispatch_get_main_queue only works for the main thread. Can I use dispatch_get_current_queue? I'm unsure of what this does when called on a background thread. The API docs say "If the call is made from any other thread, this function returns the default concurrent queue" … is that a queue associated with the thread's runloop, that's guaranteed to execute tasks on that thread?

    There's no such thing as a dispatch queue for the current thread's run loop.  The main thread's run loop also runs the main queue, as you've noted, but no other thread's run loop also runs a dispatch queue.  In fact, in general, no other queue runs on any of your threads.  libdispatch manages its own worker threads.

    On Jan 27, 2012, at 4:19 PM, Marco Tabini wrote:

    > I just use a little category on NSObject that I think I pulled from somewhere—but I can't remember where from:

    > - (void) performBlock:(void (^)(void)) block afterDelay:(NSTimeInterval) delay {
    > block = [block copy];
    >
    > [self performSelector:@selector(fireBlockAfterDelay:)
    > withObject:block
    > afterDelay:delay];
    > }
    >
    > - (void)fireBlockAfterDelay:(void (^)(void))block {
    > block();
    > }

    Believe it or not, this also works:

        [(id)^{ ... code here ... } performSelector:@selector(invoke) withObject:nil afterDelay:5.0];

    That is, you can target performSelector:withObject:afterDelay: at a block, itself, rather than some other helper object.  And, a block implements the -invoke selector to, well, invoke itself.

    Cheers,
    Ken
  • On 28/01/2012, at 1:41 PM, Ken Thomases wrote:

    > Believe it or not, this also works:
    >
    > [(id)^{ ... code here ... } performSelector:@selector(invoke) withObject:nil afterDelay:5.0];
    >
    > That is, you can target performSelector:withObject:afterDelay: at a block, itself, rather than some other helper object.  And, a block implements the -invoke selector to, well, invoke itself.

    FWIW, on stackoverflow there's a post suggesting the use of a block as a target, and using -invoke to select it. In the comments is one from bbum from last October:

    > This "works" by coincidence. It relies on private API; the invoke method on Block objects is not public and not intended to be used in this fashion.

    <http://stackoverflow.com/questions/4581782/can-i-pass-a-block-as-a-selector
    -with-objective-c
    >, about fourth answer down.

    --
    Shane Stanley <sstanley...>
    'AppleScriptObjC Explored' <www.macosxautomation.com/applescript/apps/>
  • > Where do they say that?  That's surely wrong.  The man page says that in that case it returns the default-priority global [serial] queue.

    Nevermind, I'm not paying enough attention.  I figured you'd have to return a serial queue, so my brain conveniently ignored the fact that the global default queues are all concurrent.

    FWIW here's the state of affairs on 10.7.2:

    Main queue: com.apple.main-thread (0x7fff7c2aa980)

    Default concurrent queues:
    High priority: com.apple.root.high-priority (0x7fff7c2ab240)
    High priority (overcommit): com.apple.root.high-overcommit-priority (0x7fff7c2ab300)
    Default priority: com.apple.root.default-priority (0x7fff7c2ab0c0)
    Default priority (overcommit): com.apple.root.default-overcommit-priority (0x7fff7c2ab180)
    Low priority: com.apple.root.low-priority (0x7fff7c2aaf40)
    Low priority (overcommit): com.apple.root.low-overcommit-priority (0x7fff7c2ab000)
    Background priority: com.apple.root.background-priority (0x7fff7c2ab3c0)

    Current queue as seen by:
    Default priority concurrent queue: com.apple.root.default-priority (0x7fff7c2ab0c0)
    Main thread (outside of dispatch queue): com.apple.main-thread (0x7fff7c2aa980)
    Random pthread: com.apple.root.default-overcommit-priority (0x7fff7c2ab180)
    Main queue: com.apple.main-thread (0x7fff7c2aa980)

    Note that it returns an over-commit queue if there is no other answer.  Over-commit queues differ from the normal queues in that they have no limit to the number of threads that may be servicing them concurrently (other than the general limits on thread numbers).  I expect there's a very deliberate reason why this is done, but it does open the possibility for poorly written code to over-subscribe the system and bring down the overall performance.
  • At 12:14 PM -0800 1/27/12, Jens Alfke wrote:
    > I'm really used to using
    > -performSelector:withObject:afterDelay: to make
    > something happen later. But I'd much rather use
    > a block than a target/action. I can't find any
    > API for this, however. Am I missing something?
    > What I want is basically like
    > PerformBlockAfterDelay(^{ Šcode hereŠ}, 5.0);

    Me too! PerformBlockOnThread would be nice as well.

    > It looks like I should just call dispatch_async,
    > but I'm unsure which dispatch queue to use.
    > dispatch_get_main_queue only works for the main
    > thread. Can I use dispatch_get_current_queue?

    As others have mentioned later in the thread,
    with the exception of the main queue, dispatch
    queues aren't tied to threads -- they execute
    blocks on a pool of threads managed by the system.

    Later in the thread:

    At 6:00 PM +1100 1/28/12, Shane Stanley wrote:
    > On 28/01/2012, at 1:41 PM, Ken Thomases wrote:
    >
    >> Believe it or not, this also works:
    >>
    >> [(id)^{ ... code here ... }
    > performSelector:@selector(invoke) withObject:nil
    > afterDelay:5.0];
    >>
    >> That is, you can target
    >> performSelector:withObject:afterDelay: at a
    >> block, itself, rather than some other helper
    >> object.  And, a block implements the -invoke
    >> selector to, well, invoke itself.
    >
    > FWIW, on stackoverflow there's a post suggesting
    > the use of a block as a target, and using
    > -invoke to select it. In the comments is one
    > from bbum from last October:
    >
    >> This "works" by coincidence. It relies on
    >> private API; the invoke method on Block objects
    >> is not public and not intended to be used in
    >> this fashion.
    >
    > < http://stackoverflow.com/questions/4581782/can-i-pass-a-block-as-a-selecto
    r-with-objective-c
    > ,
    > about fourth answer down.

    This reminds me that, IIRC, a block (after it's
    copied to the heap) _is_ an object. I was going
    to suggest some form of calling BlockCopy
    manually, but given the example above, you might
    something like:

    -(void)executeBlock:((^)(void) block  // <- someone help with this declaration
    {
      block();
    }

    ...

    [self performSelector:@selector(executeBlock:)
    withObject:(id)^{ ... code here ... }
    afterDelay:5.0];

    might work.

    I'm still trying to get my head around all these
    issues as well, but hoping this might help you to
    a solution. I'm interested in the solution as
    well.

    HTH,

    -Steve
  • At 12:14 PM -0800 1/27/12, Jens Alfke wrote:
    > I'm really used to using -performSelector:withObject:afterDelay: to
    > make something happen later. But I'd much rather use a block than a
    > target/action.

    At 12:23 PM -0500 1/28/12, Steve Sisak wrote:
    > This reminds me that, IIRC, a block (after it's copied to the heap)
    > _is_ an object. I was going to suggest some form of calling
    > BlockCopy manually, but given the example above, you might something
    > like:
    >
    > -(void)executeBlock:((^)(void) block  // <- someone help with this declaration
    > {
    > block();
    > }

    This was interesting enough to stick my head in the documentation and
    build a test program.

    The following appears to work:

    - (void)performBlock:(void (^)(void)) block
    {
        block();
    }

    - (IBAction) doItNow:(id)sender
    {
        [self performSelector:@selector(performBlock:) withObject:^{
            NSLog(@"Done");
        }];
    }

    - (IBAction) doItLater:(id)sender
    {
        [self performSelector:@selector(performBlock:)
                    withObject:^{
            NSLog(@"Done Later");
        }  afterDelay:1.0];
    }

    Does anyone see a problem with this technique?

    -Steve
  • On Jan 28, 2012, at 11:35 AM, Steve Sisak wrote:

    > [self performSelector:@selector(performBlock:)
    > withObject:^{
    > NSLog(@"Done Later");
    > }  afterDelay:1.0];

    Don't you need to copy the block? Or is -performSelector:withObject:afterDelay: smart enough to know to call -copy instead of -retain on the block?

    Also, I'm not entirely happy with this solution because it's tied to an object when it shouldn't need to be. So your -doItLater: method only works in the class you declare it in, and it has the side effect of keeping the receiver alive until the block runs, even if it doesn't need to be (i.e. if the block doesn't refer to 'self' at all.)

    Someone else had a solution that involved adding a category method to NSObject to invoke the block, but that seems a bit kludgy too.

    —Jens
  • At 12:39 PM -0800 1/28/12, Jens Alfke wrote:
    > On Jan 28, 2012, at 11:35 AM, Steve Sisak wrote:
    >
    >> [self performSelector:@selector(performBlock:)
    >> withObject:^{
    >> NSLog(@"Done Later");
    >> }  afterDelay:1.0];
    >>
    >
    > Don't you need to copy the block? Or is
    > -performSelector:withObject:afterDelay: smart enough to know to call
    > -copy instead of -retain on the block?

    My understanding is that copying the block happens implicitly when
    you call a function which takes a block as a parameter and doesn't
    return synchronously -- and/or it's the callee's responsibility to
    copy the block if it doesn't call it synchronously.

    I would not be surprised if calling retain on a block passed as an
    (id) does this as a side effect. (Sorry, I don't know the exact
    implementation details)

    > Also, I'm not entirely happy with this solution because it's tied to
    > an object when it shouldn't need to be.

    Agreed -- just trying to find a way to solve the problem with the API
    available.

    > So your -doItLater: method only works in the class you declare it
    > in, and it has the side effect of keeping the receiver alive until
    > the block runs, even if it doesn't need to be (i.e. if the block
    > doesn't refer to 'self' at all.)

    You (theoretically) could use any object which implements
    -performBlock: as the target -- for instance you could have a
    singleton BlockServer object which implements -performBlock: and use
    that for the target of all requests.

    > Someone else had a solution that involved adding a category method
    > to NSObject to invoke the block, but that seems a bit kludgy too.

    That would also work -- and you'd need to worry about collisions in
    Obj-C's flat name space.

    I'm still trying to figure this all out and poke around the boundary
    conditions too. I'd love to hear from "someone who 'knows'". :-)

    -Steve
previous month january 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today