等待异步回调同步

时间:2014-09-29 02:16:46

标签: ios objective-c asynchronous grand-central-dispatch

我想将SDK async api包装成同步,代码如下所示:

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;

我已经同样地阅读了这个问题 How do I wait for an asynchronously dispatched block to finish?

但是,当它在UI线程调用时,会发生死锁,因为SDK回调也会在UI线程上运行块。

如何处理?感谢。

1 个答案:

答案 0 :(得分:0)

如果SDKPlatform将其完成块调度回主队列,那么阻止主线程直到调用完成块的方法肯定会死锁,并且你无能为力。但是,无论如何,这种阻止主线程的信号量方法使得异步方法的行为类似于同步方法,这是一种不可取的方法。实际上,您应该接受异步模式,并在自己的代码中使用完成块技术。

该链接How do I wait for an asynchronously dispatched block to finish?说明了如何使用信号量使异步任务同步运行。可悲的是,这种技术被误用于惊人的频率。具体来说,在这种情况下,信号量不是您的场景中的适当模式,因为信号量模式将阻止主线程,这是我们在应用程序中永远不应该做的事情。

作为背景,信号量技术在其他线程中讨论的场景中很好,因为它是一个非常不同的技术问题。它用于测试框架的特殊情况,而不是应用程序,并且在以下情况下(a)测试本身必须在主线程上发生; (b)为了使测试框架起作用,它必须阻塞主线程,直到异步任务完成。此外,它也恰好在该测试场景中工作,因为完成块不会发生在主队列上,从而避免了您遇到的死锁问题。

在您的情况下,这三个条件都不适用。在你的情况下使用信号量技术是不可取的。

所以,让我们退一步看看你的问题。我假设你有一些看起来像这样的方法:

- (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;
}

即使您没有遇到死锁问题,这仍然是错误的模式。你可能想要的是:

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

注意,此函数具有void返回类型,但完成块返回isLoggedIn状态(并且应仅在完成块中使用,如下所示:

[self loginWithCompletionHandler:^(BOOL isLoggedIn) {
    // feel free to use isLoggedIn here
}];
// don't try to use isLoggedIn here