dispatch_sync on main queue hangs in unit test
Asked Answered
A

5

46

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?

Amary answered 19/10, 2011 at 3:13 Comment(2)
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.Rhombic
@Rhombic 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!?Bunton
F
114

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.

Flurried answered 19/10, 2011 at 6:3 Comment(2)
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 :)Oviduct
Would this be considered an example of deadlocking?Cybill
A
8

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!");
}
Agnate answered 19/10, 2011 at 3:24 Comment(0)
M
6

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.

Memento answered 19/10, 2011 at 3:33 Comment(0)
B
5

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's waiting for itself to get off the queue!

Bunton answered 24/5, 2016 at 21:45 Comment(0)
K
2

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!");
}
Kick answered 24/4, 2015 at 15:1 Comment(3)
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.Gibert
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.Levey
@Levey 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-threadGibert

© 2022 - 2024 — McMap. All rights reserved.