Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dispatch_sync on main queue hangs in unit test

I was having some trouble unit testing some grand central dispatch code with the built in Xcode unit testing framework, SenTestingKit. I managed to boil my problem done to this. I have a unit test that builds a block and tries to execute it on the main thread. However, the block is never actually executed, so the test hangs because it's a synchronous dispatch.

- (void)testSample {

    dispatch_sync(dispatch_get_main_queue(), ^(void) {
        NSLog(@"on main thread!");
    });

    STFail(@"FAIL!");
}

What is it about the testing environment that causes this to hang?

like image 321
Drewsmits Avatar asked Oct 19 '11 03:10

Drewsmits


4 Answers

dispatch_sync runs a block on a given queue and waits for it to complete. In this case, the queue is the main dispatch queue. The main queue runs all its operations on the main thread, in FIFO (first-in-first-out) order. That means that whenever you call dispatch_sync, your new block will be put at the end of the line, and won't run until everything else before it in the queue is done.

The problem here is that the block you just enqueued is at the end of the line waiting to run on the main thread—while the testSample method is currently running on the main thread. The block at the end of the queue can't get access to the main thread until the current method (itself) finishes using the main thread. However dispatch_sync means Submits a block object for execution on a dispatch queue and waits until that block completes.

like image 153
BJ Homer Avatar answered Nov 07 '22 21:11

BJ Homer


The problem in your code is that no matter whether you use dispatch_sync or dispatch_async , STFail() will always be called, causing your test to fail.

More importantly, as BJ Homer's explained, if you need to run something synchronously in the main queue, you must make sure you are not in the main queue or a dead-lock will happen. If you are in the main queue you can simply run the block as a regular function.

Hope this helps:

- (void)testSample {

    __block BOOL didRunBlock = NO;
    void (^yourBlock)(void) = ^(void) {
        NSLog(@"on main queue!");
        // Probably you want to do more checks here...
        didRunBlock = YES;
    };

    // 2012/12/05 Note: dispatch_get_current_queue() function has been
    // deprecated starting in iOS6 and OSX10.8. Docs clearly state they
    // should be used only for debugging/testing. Luckily this is our case :)
    dispatch_queue_t currentQueue = dispatch_get_current_queue();
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    if (currentQueue == mainQueue) {
        blockInTheMainThread();
    } else {
        dispatch_sync(mainQueue, yourBlock); 
    }

    STAssertEquals(YES, didRunBlock, @"FAIL!");
}
like image 36
nacho4d Avatar answered Nov 07 '22 21:11

nacho4d


If you are on the main queue and synchronously wait for the main queue to be available you will indeed wait a long time. You should test to make sure you are not already on the main thread.

like image 6
NJones Avatar answered Nov 07 '22 22:11

NJones


Will you ever get out of house if you must wait for yourself to get out house first? You guessed right! No! :]

Basically if:

  1. You are on FooQueue. (doesn't have to be main_queue)
  2. You call the method using sync ie in a serial way and want to execute on FooQueue.

It will never happen for same reason that you will never get out of house!

It won't ever get dispatched because it has to wait for itself to get off the queue!

like image 4
mfaani Avatar answered Nov 07 '22 20:11

mfaani