dispatch_sync on main queue hangs in unit test

21,913

Solution 1

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.

Solution 2

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!");
}

Solution 3

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.

Solution 4

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!

Solution 5

To follow up, since

dispatch_get_current_queue()

is now deprecated, you can use

[NSThread isMainThread]

to see if you are on the main thread.

So, using the other answer above, you could do:

- (void)testSample 
{
    BOOL __block didRunBlock = NO;
    void (^yourBlock)(void) = ^(void) {
        NSLog(@"on main queue!");
        didRunBlock = YES;
    };

    if ([NSThread isMainThread])
        yourBlock();
    else
        dispatch_sync(dispatch_get_main_queue(), yourBlock);

    STAssertEquals(YES, didRunBlock, @"FAIL!");
}
Share:
21,913

Related videos on Youtube

Drewsmits
Author by

Drewsmits

Mechanical Engineer that wishes he had taken more CS courses. Currently working on all things mobile at Nest Labs.

Updated on January 04, 2020

Comments

  • Drewsmits
    Drewsmits over 4 years

    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?

    • D.C.
      D.C. over 12 years
      Good question and I look forward to the correct answer. I have found several times that using dispatch_sync on the main queue ends up in deadlock so I just avoid it in general.
    • mfaani
      mfaani over 7 years
      @D.C. several times or ALWAYS? I'm curious as in how can you dispatch_sync(dispatch_get_main_queue() while on the main thread won't create dead lock!?
  • Tim Erickson
    Tim Erickson about 10 years
    Lovely. I second the thanks. I have code I want to force to run in the main thread, so this looks like the way to do it. But (a) how can I tell if I'm in some other thread and (b) How did my non-main-thread code get put there in the first place? Any clues would be welcome :)
  • Alejandro Iván
    Alejandro Iván about 7 years
    Being on the main thread doesn't ensure you're running in the main queue. They're different things. The main queue runs on the main thread, but there could be other queues running on it.
  • Pescolly
    Pescolly over 6 years
    Would this be considered an example of deadlocking?
  • emrepun
    emrepun about 4 years
    Can you justify this statement with any documentation or anything? @AlejandroIván Moreover, even with what you are saying, this code should be safe. Because, main queue can only run on main thread, and we are obviously not on main thread when else clause is executed. So synchronising with main queue should never create problems or deadlocks with this way.
  • Alejandro Iván
    Alejandro Iván about 4 years
    @emrepun if you get into the if condition (I mean, you don't get into the else), you can't ensure you're on the main queue, only in the main thread (where the main queue runs on). Check out this blog entry: blog.benjamin-encz.de/post/main-queue-vs-main-thread