1

I want to wrap a SDK async api to sync, code looks like this:

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block BOOL _isLogined;
__block BOOL _isCallback = NO;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^  {
    //Put your heavy code here it will not block the user interface
    [[SDKPlatform defaultPlatform] SDKIsLogined:^(BOOL isLogined){
        _isLogined = isLogined;
        _isCallback = YES;
        dispatch_semaphore_signal(sema);
    }];
});
while (!_isCallback) {
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
return _isLogined;

I already read the question similarly How do I wait for an asynchronously dispatched block to finish?

but, when it was call at UI thread, deadlock happen because the SDK callback run the block at UI thread too.

How to deal it? Thanks.

Community
  • 1
  • 1
Eric Wang
  • 11
  • 2
  • unless I misunderstand something the approach with semaphores and so on is totally incorrect, Eric. All you do is (1) run the code and (2) when finished, alert the main thread. Then you can do the next thing. It's absolutely commonplace, example below! – Fattie Sep 29 '14 at 06:28
  • You need to increase your semaphore count by "1" (using a "1" instead of 0) when it's created. Creating a semaphore with no room for accessing a shared resource is going to cause deadlock when dispatch_semaphore_wait is called. – user298261 Jun 04 '15 at 20:32

1 Answers1

1

If SDKPlatform is dispatching its completion block back to the main queue, then your approach of blocking the main thread until the completion block is called will certainly deadlock and there's not much you can do about that. But this semaphore approach to block the main thread so you can make an asynchronous method behave like a synchronous one is an inadvisable approach, anyway. You should actually embrace the asynchronous patterns and employ the completion block techniques in your own code.

That link, How do I wait for an asynchronously dispatched block to finish?, illustrates how one can use semaphores to make asynchronous task run synchronously. Sadly, this technique is misused with alarming frequency. Specifically, in this case, the semaphore is not the appropriate pattern in your scenario because the semaphore pattern will block the main thread, something that we should never do within an app.

By way of background, the semaphore technique is fine in the scenario discussed in that other thread, because it's a very different technical problem. It is being use in a special situation of a testing framework, not an app, and in a situation where (a) the test, itself, must happen on the main thread; (b) for the testing framework to function, it must block the main thread until the asynchronous task completes. Furthermore, it also happens to work in that testing scenario, because the completion block does not take place on the main queue, avoiding the deadlock issue you're experiencing.

None of these three conditions hold in your case. To use the semaphore technique in your situation is inadvisable.

So, let's step back and look at your problem. I'm assuming that you have some method that looks like:

- (BOOL) login 
{
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    __block BOOL _isLogined;
    __block BOOL _isCallback = NO;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^  {
        //Put your heavy code here it will not block the user interface
        [[SDKPlatform defaultPlatform] SDKIsLogined:^(BOOL isLogined){
            _isLogined = isLogined;
            _isCallback = YES;
            dispatch_semaphore_signal(sema);
        }];
    });
    while (!_isCallback) {
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    }
    return _isLogined;
}

Even if you didn't have your deadlock problem, this is still the wrong pattern. What you probably want is something like:

- (void)loginWithCompletionHandler:(void (^)(BOOL isLoggedIn))completionHandler
{
    [[SDKPlatform defaultPlatform] SDKIsLogined:^(BOOL isLoggedIn){
        if (completionHandler) {
            completionHandler(isLoggedIn);
        }
    }];
}

Note, this function has a void return type, but rather the isLoggedIn state is returned by the completion block (and should be only used within the completion block, like so:

[self loginWithCompletionHandler:^(BOOL isLoggedIn) {
    // feel free to use isLoggedIn here
}];
// don't try to use isLoggedIn here
Community
  • 1
  • 1
Rob
  • 371,891
  • 67
  • 713
  • 902