App freezes on Xcode11 when using dispatch group or semaphore

Hello,


After updating to Xcode 10 I started having an issue where the app freezes when waiting for a semaphore. I tried changing to dispatch groups but the same thing happens: it works normally with Xcode 10 and freezes with Xcode 11 (both stable and beta).


The code is basically this:


result = false

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_sync(dispatch_get_main_queue(), myBlock);


dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

return result;


Inside myBlock I display an AlertView that signals the semaphore when the uses presses either button.

Simply changing to dispatch_async stops the freezing, but also breaks the functionality since I need the answer from the AlertView before returning.

Replies

I’m confused by your code. If you’re using a semaphore, why do you need to

dispatch_sync
? The secondary thread will block on the semaphore until the main thread calls
dispatch_semaphore_signal
, so why does it need to call the main thread synchronously?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

ps DTS is closed 25…29 Nov in observance of the US Thanksgiving holiday.

I was not the one who wrote the initial code, but from what I understand, the issue is that this method is originally called from another thread, several steps away:


> dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{...


And since what I need is to do is show an AlertView and wait for the user input, it needs to be done in the main thread, correct?

And since what I need is to do is show an AlertView and wait for the user input, it needs to be done in the main thread, correct?

Right. And you have to wait for the main thread to respond. That all makes sense. What doesn’t make sense is using two different ‘sync’ primitives to achieve the second goal. Specifically:

  • You call

    dispatch_sync
    , which runs the block on the main thread and doesn’t return until the block returns.
  • You also use a semaphore to block the secondary thread until the alert completes.

If you have the latter, you don’t need the former. You can

dispatch_async
to the main thread because you don’t need to wait until the block that posts the alert is finished. Rather, you need to wait until the alert is done, and that’s what the semaphore does for you.

Notwithstanding the above, this technique is very concerning. If any code on your main thread has a dependency on this secondary thread, or a resource held by the secondary thread, you run the risk of deadlock.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

If I only use dispatch_sync it does not achieve the result I want, the function returns before the block is executed.

I think this happens because this function is called from a thread that´s not the main one.


Here´s the code below, when I put breakpoints, it executes in the following order:


1. Block (presentViewController)

2. function returns with result = NO

3. Alert appears on screen.

4. User presses Yes/No

5. Executes AlertAction


At this point, since the function has already returned, it does not achieve the expected result.


__block BOOL result = NO;

// dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_sync(dispatch_get_main_queue(), ^{

UIViewController *rootController = [IOSUtil visibleViewController];

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[self getResourceText:caption]

message:[self getResourceText:message]

preferredStyle:UIAlertControllerStyleAlert];

[alertController addAction:[UIAlertAction actionWithTitle:cancelButtonText

style:UIAlertActionStyleDestructive

handler:^(UIAlertAction *action) {

[LayoutIOS internalShowProgressControl];

// dispatch_semaphore_signal(semaphore);

}]];

[alertController addAction:[UIAlertAction actionWithTitle:okButtonText

style:UIAlertActionStyleDefault

handler:^(UIAlertAction *action) {

result = YES;

[LayoutIOS internalShowProgressControl];

// dispatch_semaphore_signal(semaphore);

}]];

[LayoutIOS internalHideProgressControl];

[rootController presentViewController:alertController animated:YES completion:nil];

});

// dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

return result;

Also, if I use dispatch_async and the semaphore, the app freezes the moment the user hits the yes/no button, it never reaches the AlertAction.

if I use

dispatch_async
and the semaphore, the app freezes the moment the user hits the yes/no button

I can’t run your code because it has dependencies on your various utility routines, so I rewrote it slightly to show this technique in a vanilla fashion:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [NSThread detachNewThreadWithBlock:^{
        while (YES) {
            NSLog(@"will delay");
            [NSThread sleepForTimeInterval:5.0];
            NSLog(@"did delay");
            dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"will show alert");
                UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Hello Cruel World!"
                    message:nil
                    preferredStyle:UIAlertControllerStyleAlert
                ];

                [alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
                    style:UIAlertActionStyleDestructive
                    handler:^(UIAlertAction * action) {
                        NSLog(@"alert cancel");
                        dispatch_semaphore_signal(semaphore);
                    }
                ]];

                [alert addAction:[UIAlertAction actionWithTitle:@"OK"
                    style:UIAlertActionStyleDefault
                    handler:^(UIAlertAction * action) {
                        NSLog(@"alert OK");
                        dispatch_semaphore_signal(semaphore);
                    }
                ]];

                [self presentViewController:alert animated:YES completion:nil];
                NSLog(@"did show alert");
            });
            NSLog(@"will wait for alert");
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"did wait for alert");
        }
    }];
}

If I add this code to a new app created from the iOS > Single View App template, it does what I’d expect, that is, the secondary thread runs and, every 5 seconds, it posts an alert and waits for the response.

This is using Xcode 11.3 on the iOS 13.3 simulator.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I tried your code and the same thing happens, the function returns before the alert is shown, and the alert is displayed after that. When the user presses either button, the application freezes.


Might be due to the fact that this function is called from inside another dispatch:


> dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{...


Edit: after trying on a new app and using the above additional dispatch async to wrap it I confirmed that it does work on a void function, but when the function returns a value, it always happens before the block is executed.

voice function

Huh? Perhaps you can tweak my example to show the problem you’re having?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Sorry, I meant to say "void" but the corrector changed it to "voice".


I ran your code on a new app trying to create a similar solution to what I have in mine: making the function return a bool value and wrapping the function call in a dyspatch_async like the one in my previous comment. And it does work as expected, which means there is something else in my app that is causing this. Maybe it´s the deadlock situation you described above. Thank you for helping me clear this out, I will report back when I find the problem.