【问题标题】:Synchronisation of two async calls两个异步调用的同步
【发布时间】:2014-06-16 09:10:12
【问题描述】:

早上好,

我的应用程序启动时有两个异步调用: 一种用于获取推送通知的设备令牌,另一种用于尝试将用户登录到 Facebook。

如果我没有两个异步调用的结果,我不能继续申请流程。

目前,我正在做这样的事情:两个布尔值 didFBConnectdidDeviceTokenObtained

我正在一个线程中运行这个(显然很糟糕):

while(!(didFBConnect && didDeviceTokenObtained)){ 
}
myFunctionToContinue();

你知道处理这种情况的正确方法是什么吗?

BR

【问题讨论】:

  • 坏消息,APNS注册失败回调并不总是被调用。
  • 是的,这是我积压中的另一个问题:p

标签: ios objective-c multithreading asynchronous


【解决方案1】:

一个可能的解决方案是创建一个 GCD 调度组。看看下面的这个例子(简单的应用程序),在同一个方法上,你将异步调用不在主线程中,你将: 1.获取全局队列 2.创建调度组 3.在全局队列和组中调度异步两个作业(FB连接和推送通知令牌) 4.添加调度组等待组内作业完成 5. 等待后添加你的 myFunctionToContinue()

因此,您要做的是将这两个作业排入队列,但将它们分组,然后等待完成。这比在等待两个作业完成时让线程处于循环中要高效得多。

之所以不在主线程中调用“gcdGroup”,而是在专用串行队列中调用,是为了避免在组等待时阻塞主线程。

一般来说,如果您的并发作业是高度异步的并且提供了很大的灵活性,那么使用 GCD 组非常方便,因为您可以添加其他条件而不影响代码(只需创建块并放入组中)。此外,您还可以调度创建自己的组的块,从而创建复杂的组图。


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

    dispatch_queue_t serial_queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial_queue, ^{
        [self gcdGroup];
    });

    NSLog(@"Main thread not blocked...");

    return YES;
}

- (void)gcdGroup {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, queue, ^{
        NSLog(@"Start First job");
        sleep(5);
        NSLog(@"End First job");
    });

    dispatch_group_async(group, queue, ^{
        NSLog(@"Start second job");
        sleep(arc4random()%5);
        NSLog(@"End second job");
    });

    NSLog(@"Now waiting for group to finish...");

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    NSLog(@"End of group");

}

【讨论】:

  • 是的,在我看来,这比公认的解决方案更合适。
  • 问题是我不自己做电话,我只得到结果感谢代表
  • 是的,当您有来自异步请求的委托回调时,此解决方案不适合(或无法轻松实现)。如果您仍想使用 gcd 解决方案,您可以使用 gcd 信号量而不是组:仍然将您的“gcdGroup”方法提交到串行队列(以避免用信号量阻塞主线程)您可以调用您的异步方法(即注册推送和 FB 连接),然后在委托方法上发出信号量(每个方法的信号量),并将两个信号量等待信号放在“callMyFunction()”调用之前。
【解决方案2】:

您应该在登录和令牌调用完成后调用相同的函数并检查一次条件,不要在 while 循环中执行此操作:

- (void)faceBookSuccess {
    ...
    didFBConnect = YES;
    [self tryToContinue];
}                      

- (void)tokenSuccess {
    ...
    didDeviceTokenObtained = YES;
    [self tryToContinue];
}                 

- (void)tryToContinue {
    if(didFBConnect && didDeviceTokenObtained) {
        myFunctionToContinue();
    }
}

【讨论】:

  • 多么聪明和简单……感谢您的意见!
  • 这种方法唯一需要注意的是myFunctionToContiue可能不会在主线程上被调用,如果你更新任何UI元素会导致你的问题-我建议dispatch_async(dispatch_get_main_queue(), ^{ myFunctionToContinue(); });
  • 还有myFunctionToContinue 会被调用两次的风险@viggio24 的解决方案更安全
  • 是的,如果两个回调不在主线程上,它可能会被调用两次。您可以在调用tryToContinu 之前切换到主线程。
【解决方案3】:

看看NSCondition:

https://developer.apple.com/library/mac/documentation/cocoa/conceptual/Multithreading/ThreadSafety/ThreadSafety.html

https://developer.apple.com/library/ios/documentation/cocoa/reference/NSCondition_class/Reference/Reference.html

您应该signal 两个条件而不是设置两个布尔变量,并为这两个条件设置主线程/继续线程wait(无论以何种顺序)。

我建议不要使用普通变量进行同步,因为如果不对 CPU 和编译器做出假设,就无法保证变量按预期顺序分配。如果你这样做,也许你会很幸运,但它不会起作用,否则你会花费大量时间调试竞争条件,这是你应得的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-24
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 2021-06-27
    • 2011-09-21
    • 1970-01-01
    • 2016-03-24
    相关资源
    最近更新 更多