线程中的异步转同步 —— 应用实例

应用的实例背景交代:

SDK提供给APP层一个接口。

  • 假设app需要调用一个sdk中的接口,获取执行一个操作的结果。比如
    -(BOOL)showMeResult;
    
  • 但是实际情况是,我SDK中对这个执行结果的响应是分散在两个不同的函数中的。好比我们交易中的二次授权,
    • A函数中发起二次授权,需要等待一段时间对IC卡进行写卡操作
    -(BOOL)FunctionA {  // ------ 伪代码,仅为说明用
          return [self startSecondIssuance]; 
    }
    
    • B函数作为监听,写卡完成后才能获取到二次授权的结果
    -(void)FunctionB { // ------ 伪代码,仅为说明用
    if (self.isSuccess) {
    NSLog(@"二次授权成功,在这里才能通知'--showMeResult--'的结果");
    } else
    NSLog(@"二次授权失败,在这里才能通知'--showMeResult--'的结果");
    }
    
    

问题综述

  • 等于是APP调用 -(BOOL)showMeResult -> 调用SDK的-(BOOL)FunctionA -> 发起监听,至回调到 -(void)FunctionB中才能取到 APP 调用函数的执行结果
  • APP调用时候,代码是一行一行执行的,而我在-(BOOL)FunctionA中无法知悉-(void)FunctionB中的结果何时才能完成。这里就要用到线程阻塞。

解决方案

前提:

APP调用-(BOOL)showMeResult的时候,必须要另开一个线程来做等待,因为不论是下面的方案一,还是方案二,都会阻塞线程。阻塞线程的操作,不能在主线程做,否则造成主线程死锁。

{   // 创建队列开辟线程
    dispatch_queue_t globalQueue = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
    // 执行
    dispatch_async(globalQueue, ^{
    NSLog(@"另开线程,开始工作!");
    BOOL rslt = [self showMeResult];
    });
}

方案一:使用while循环

  • 优点:简单,除了上面说到的APP层调用的时候要用到GCD线程以外,在SDK层不需要再写任何的线程开辟。
  • 缺点:while循环会让CPU一直处于读状态,没有进入休眠,会消耗大量的资源。
// ------------- app 层调用 -------------
 -(IBAction)WaitForValue:(UIButton *)sender {
      dispatch_queue_t globalQueue = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
      dispatch_async(globalQueue, ^{
      // task
      NSLog(@"另开线程,开始工作!");
      BOOL rslt = [self WaitForValue_SDK];
    });
}
// ------------- SDK层 -------------
 -(BOOL)WaitForValue_SDK { // 
      NSLog(@"wait finished...");
      while (!self.Info) {
          NSLog(@"+++++ keep waiting... +++++++");
      }
      NSLog(@"出循环,有返回值!");
      return YES;
}
//在另外一个地方对self.Info赋值 
-(IBAction)Resume:(UIButton *)sender {
      NSLog(@"1.请继续");
      self.Info = @"resume";
}

方案二:用NSCondition做线程锁,线程等待结果来完成

  • 优点:通知线程休眠,等待值完成
  • 缺点:代码比较多(其实也不算什么缺点)
  • NSCondition 介绍
// ------------- app 层调用 -------------
- (IBAction)WaitForValue_UsingThread:(UIButton *)sender {
    dispatch_queue_t globalQueue = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
    dispatch_async(globalQueue, ^{
        // task
        NSLog(@"另开线程,开始工作!");
        [self WaitForValue_Thread];
    });
}
// ------------- SDK 层调用 ------------- 
-(BOOL)WaitForValue_Thread {
    self.condition = [[NSCondition alloc] init];
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        if(!self.Thread_Info) {
            NSLog(@"thread 0.开始等待赋值..");
            [self.condition wait];
        }
        NSLog(@"thread 2.恢复了…………");
        NSLog(@"thread 3.Thread_Info = %@",self.Thread_Info);
        [self.condition unlock];
    });
    
    NSLog(@"wait finished...");
    NSLog(@"4.出循环,有返回值!");
    return YES;
}

- (IBAction)Resume_Thread:(UIButton *)sender {
    //    dispatch_queue_t Q = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
    //
    //    dispatch_async(Q, ^{
    //        [self.condition lock];
    //        NSLog(@"1.请继续");
    //        self.Info = @"resume";
    //        [self.condition signal];
    //        [self.condition unlock];
    //    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        NSLog(@"thread 1.请继续");
        self.Thread_Info = @"resume";
        [self.condition signal];
        [self.condition unlock];
    });
}

日志打印结果

2016-06-05 20:33:41.602 ThreadCtrl[19015:625341] 另开线程,开始工作!
2016-06-05 20:33:41.603 ThreadCtrl[19015:625341] thread 0.开始等待赋值..
2016-06-05 20:33:43.859 ThreadCtrl[19015:625339] thread 1.请继续
2016-06-05 20:33:43.860 ThreadCtrl[19015:625341] thread 2.恢复了…………
2016-06-05 20:33:43.860 ThreadCtrl[19015:625341] thread 3.Thread_Info = resume
2016-06-05 20:33:43.860 ThreadCtrl[19015:625341] wait finished...
2016-06-05 20:33:43.861 ThreadCtrl[19015:625341] 4.出循环,有返回值!

SDK 层调用GCD的说明

  • 线程等待部分记得 必须用 dispatch_sync同步方式来做,否则会先执行返回值的部分
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        // to do task...
        [self.condition wait];
        [self.condition unlock];
    });

    NSLog(@"wait finished...");
    return YES;
  • 上面的 waitresume中使用到的GCD队列可以是同一个队列,也可以是不同的队列,似乎并不影响结果。也就是下面注释和没注释的部分结果是一样的
 //    dispatch_queue_t Q = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
    //    dispatch_async(Q, ^{
    //        [self.condition lock];
    //        [self.condition signal];
    //        NSLog(@"1.请继续");
    //        self.Info = @"resume";
    //        [self.condition unlock];
    //    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.condition lock];
        [self.condition signal];
        NSLog(@"thread 1.请继续");
        self.Thread_Info = @"resume";
        [self.condition unlock];
    });

使用 GCD

GCD 中提供了一个信号发送/接收的方法,类似于上面提到的进程锁,可以将异步转化为同步。
下面是示例代码

对外暴露的是一个等待返回 BOOL 类型的结果。

// 等到同步执行的结果
- (BOOL)waiForAsyncExecution {

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//创建信号量
    __block BOOL result = NO;
    
    [[SomeNetworkClass sharedInstance] getAsyncResult:^(NSString *unit) {
        if (unit) {
            [self doSomething];
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [SomeClass updateUI];// 更新 UI部分在主线程中执行,所以又放到了一个dispatch_async 中
                dispatch_semaphore_signal(semaphore);//发送信号量
            });
        } else {
            dispatch_semaphore_signal(semaphore);//发送信号量
        }
    }];
    
    dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);//同步等待信号量
    return result;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容