zoukankan      html  css  js  c++  java
  • iOS 等待for循环里的异步任务完成再进行其他操作的一个解决办法 -- 信号量+串行队列卡for小循环

    转自:https://blog.csdn.net/qq_34417314/article/details/80449484

    for循环里的异步操作

    开发中经常会遇到这样一些情况,比如:
    1.登录失败后的多次登录重连场景。
    2.在一个for循环遍历里,有多种异步操作,需要在所有的异步操作完成后,也就是for循环的遍历结束后,再去执行其他操作,但是不能卡主线程,这时候就需要用其他方法了。

    我遇到的需求是,在一个for循环里有数据库的查询操作以及网络请求操作,然后将数据库的查询和网络请求的结果添加到一个临时数组中,最后等for循环结束后,将临时数组通过block回传出去。
    解决办法如下:

    • 串行队列配合信号量

    因为for循环不能卡主线程,所以我们首先要创建一个串行的队列(并发的不可以),然后通过信号量来控制for循环,下面是模拟的一个操作:

     1 printf("处理前创建信号量,开始循环异步请求操作
    ");
     2 
     3 // 1.创建一个串行队列,保证for循环依次执行
     4         dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
     5 
     6 // 2.异步执行任务
     7 dispatch_async(serialQueue, ^{
     8     // 3.创建一个数目为1的信号量,用于“卡”for循环,等上次循环结束在执行下一次的for循环
     9     dispatch_semaphore_t sema = dispatch_semaphore_create(0);
    10    
    11     for (int i = 0; i<5; i++) {
    12         // 开始执行for循环,让信号量-1,这样下次操作须等信号量>=0才会继续,否则下次操作将永久停止
    13     
    14         printf("信号量等待中
    ");
    15         // 模拟一个异步任务
    16         NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://github.com"]];
    17         NSURLSession *session = [NSURLSession sharedSession];
    18         NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    19         // 本次for循环的异步任务执行完毕,这时候要发一个信号,若不发,下次操作将永远不会触发
    20             [tampArray addObject:@(i)];
    21             NSLog(@"本次耗时操作完成,信号量+1 %@
    ",[NSThread currentThread]);
    22             dispatch_semaphore_signal(sema);
    23             
    24         }];
    25         [dataTask resume];
    26         dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    27     }
    28     
    29     NSLog(@"其他操作");
    30     for (NSNumber *num in tampArray) {
    31         NSLog(@"所有操作完成后的操作--->   %@
    ",num);
    32     }
    33     
    34 });

    以上就是一个简单的卡for循环的实例

    • 实际场景中的应用

    我遇到场景:
    1.从相册里取若干原图,先获取到缩略图的数组,后根据缩略图信息取原图,这是一个可同步可异步的操作,鉴于卡顿考虑,采用异步取原图的操作,

     1  NSMutableArray *tmpPhotoes = [NSMutableArray array];
     2             // 这里用信号量卡 for 循环 来获取原图,等全部获取完成再刷新UI
     3             dispatch_queue_t serialQueue = dispatch_queue_create("getOriginalQueue", DISPATCH_QUEUE_SERIAL);
     4             
     5             dispatch_async(serialQueue, ^{
     6                 
     7                 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
     8                 
     9                 [assets enumerateObjectsUsingBlock:^(PHAsset *tmpAsset, NSUInteger idx, BOOL * _Nonnull stop) {
    10                     
    11                     /* 这里换成系统的方法也是一样的,[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage *result, NSDictionary *info) {
    12     }];*/ 
    13                         __block int time = 0;  // 由于获取原图会有2次回调,PHImageResultIsDegradedKey为0时才为原图
    14                     [[TZImageManager manager] getOriginalPhotoWithAsset:tmpAsset newCompletion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
    15                         // 原图
    16                         if ([[info jk_stringForKey:PHImageResultIsDegradedKey] isEqualToString:@"0"]) {
    17                             [tmpPhotoes addObject:photo];
    18                         }
    19                         time++;
    20                         if (time == 2) {
    21                             dispatch_semaphore_signal(sema);
    22                         }
    23                     }];
    24                     
    25                     dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    26                     
    27                 }];
    28 
    29                 dispatch_async(dispatch_get_main_queue(), ^{
    30                     if (tmpPhotoes.count) {
    31                         [self refreshImageDataWithPhotos:tmpPhotoes assets:assets];
    32                     }
    33                 });
    34             });

    2.外界多个渠道随时可能来查询一个会话列表数组的数据,但是原始的数组里没有包含想要的东西,需要包装一下再供外界使用。首先需要创建一个条件锁,防止这个列表数据被多方访问造成资源抢夺,其次循环遍历原始数组,通过数据库查询和网络请求操作重新包装会话列表里的数据,最后等待for循环结束,将包装过的数组回传给外界以供使用。

     1 /** 会话列表 */
     2 -(void)getConversationList:(void(^)(NSArray *conversationList))handler {
     3 // 条件锁,若当前资源可获取则进行下一步,否则等待条件锁完成才能访问
     4 if ([self.lock obtain]) {
     5     BBLog(@"BigBang- 获取资源中");
     6     // 会话列表
     7     NSArray *oriList = [[BBConversationManager manager] getConversationList];
     8     
     9     dispatch_queue_t serailQueue = dispatch_queue_create("conversationQueue", DISPATCH_QUEUE_SERIAL);
    10     dispatch_async(serailQueue, ^{
    11         
    12         // 每次重置,防止 dispose 
    13         self.sema = nil;
    14         self.sema = dispatch_semaphore_create(1);
    15         BBLog(@"BigBang- CrashInfo -- 信号量创建 当前线程---%@",[NSThread currentThread]);
    16         // 临时数组 存储变换后的会话模型
    17         __block NSMutableArray *temList = [NSMutableArray array];
    18         if (oriList.count > 0) {
    19             [oriList enumerateObjectsUsingBlock:^(RCConversation *conversation, NSUInteger idx, BOOL * _Nonnull stop) {
    20                 
    21                 NSString *identifier = conversation.targetId;
    22                 RCMessage *message = [[RCIMClient sharedRCIMClient] getMessage:conversation.lastestMessageId];
    23                 
    24                 if ([conversation conversationType] == ConversationType_PRIVATE || [conversation conversationType] == ConversationType_SYSTEM) {
    25                     
    26                     BBLog(@"BigBang- CrashInfo 信号量-1");
    27                     dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
    28                     
    29                     // 本地存在,直接从本地数据库取 这里是一个数据库的查询操作,是在子线程中进行的
    30                     if ([BBUserTable isUserExist:identifier]) {
    31                         
    32                         BBConversationListModel *model = [[BBConversationListModel alloc] initWithRCMessage:message userInfo:[BBUserTable gerUserWithID:identifier]];
    33                         model.unreadMessageCount = conversation.unreadMessageCount;
    34                         if ([conversation conversationType] == ConversationType_SYSTEM) {
    35                             [[BBConversationManager manager] setConversationToTop:ConversationType_SYSTEM targetId:identifier isTop:YES];
    36                         }
    37                         
    38                         [temList addObject:model];
    39                         // 从数据库获取完成 发送信号 继续下一次取值
    40                         dispatch_semaphore_signal(self.sema);
    41                         BBLog(@"BigBang- CrashInfo 数据库 信号量+1");
    42                         
    43                     }else {
    44                         
    45                         //  本地不存在 调接口请求数据 这里是一个网络请求操作,无论请求成功与否,都要将信号量恢复
    46                         [[BBDataManager manager] asyncGetUserInfoWithUserID:identifier result:^(NSDictionary *userInfo) {
    47                             
    48                             BBUserInfo *newUserInfo = [[BBUserInfo alloc] initWithUserID:identifier userName:[NSString stringWithFormat:@"%@",userInfo[@"nickname"]] avatar:[NSString stringWithFormat:@"%@",userInfo[@"avatar"]] level:[NSString stringWithFormat:@"%@",userInfo[@"level"]]];
    49                             BBConversationListModel *model = [[BBConversationListModel alloc] initWithRCMessage:message userInfo:newUserInfo];
    50                             model.unreadMessageCount = conversation.unreadMessageCount;
    51                             
    52                             if ([conversation conversationType] == ConversationType_SYSTEM) {
    53                                 [[BBConversationManager manager] setConversationToTop:ConversationType_SYSTEM targetId:identifier isTop:YES];
    54                             }
    55                             
    56                             [temList addObject:model];
    57                             
    58                             BBLog(@"BigBang- CrashInfo 网络请求 信号量+1");
    59                             dispatch_semaphore_signal(self.sema);
    60                             [BBUserTable saveUserInfoWithModel:newUserInfo];
    61                             
    62                         } error:^(NSError *error){
    63                             
    64                             BBLog(@"BigBang- CrashInfo 网络请求失败 信号量+1");
    65                             dispatch_semaphore_signal(self.sema);
    66                             
    67                         }];
    68                     }
    69                 }
    70             }];
    71             // 结束操作后,将锁打开,以供后面的访问
    72             if (handler) {
    73                 if (temList.count) {
    74                     dispatch_async(dispatch_get_main_queue(), ^{
    75                         handler(temList);
    76                     });
    77                     BBLog(@"BigBang- CrashInfo 获取数据完毕,回传显示  当前线程--%@ ",[NSThread currentThread]);
    78                 }
    79             }
    80             [self.lock finish];
    81         }else {
    82             if (handler) {
    83                 handler(nil);
    84             }
    85             [self.lock finish];
    86         }
    87     });
    88     
    89 }else {
    90     BBLog(@"BigBang- 抢夺资源中,等待当前获取资源结束继续进行");
    91 }
     }

    上面的条件锁的文件如下: .h 文件 这里的 < > 不能用,请自行替换 ‘ 为 < >
    使用方法为:

    self.lock = [[BBResourceLock alloc] init];
    if ([self.lock obtain]) {
    // 各种任务

    // 结束后将锁关闭
    [self.lock finish];

    }

    具体的文件如下:
    import ‘Foundation/Foundation.h’

    @interface BBResourceLock : NSObject

    /**

    • 获取资源,如果资源已经被获取过则返回 NO,否则返回 YES
    • @return 如果资源已经被获取过则返回 NO,否则返回 YES
      */
      -(BOOL)obtain;

    /**
    等待资源可用
    如果资源没有被获取过 则获取资源并返回YES
    如果资源被其他线程获取则等待,直到资源可用并返回 YES
    如果资源是被同一线程获取则不会等待,并且返回NO
    @return YES/NO
    */
    -(BOOL)wait;

    /**
    释放占用的资源
    */
    -(void)finish;

    @end

    .m文件
    #import “BBResourceLock.h”

    @interface BBResourceLock ()
    /** 当前线程 /
    @property (nonatomic,strong) NSThread mOccupiedThread;
    / 条件 */
    @property (nonatomic,strong) NSCondition *mCondition;
    @end

    @implementation BBResourceLock

    • (instancetype)init
      {
      self = [super init];
      if (self) {
      self.mCondition = [[NSCondition alloc] init];
      }
      return self;
      }

    /**

    • 获取资源,如果资源已经被获取过则返回 NO,否则返回 YES

    • @return 如果资源已经被获取过则返回 NO,否则返回 YES
      */
      -(BOOL)obtain {

      @synchronized(self.mCondition) {
      if (self.mOccupiedThread) {
      return NO;
      }else {
      self.mOccupiedThread = [NSThread currentThread];
      return YES;
      }
      }
      }

    /**
    等待资源可用
    如果资源没有被获取过 则获取资源并返回YES
    如果资源被其他线程获取则等待,直到资源可用并返回 YES
    如果资源是被同一线程获取则不会等待,并且返回NO
    @return YES/NO
    */
    -(BOOL)wait {
    @synchronized (self.mCondition) {
    if (!self.mOccupiedThread) {
    return [self obtain];
    }
    if (self.mOccupiedThread == [NSThread currentThread]) {
    return YES;
    }
    [self.mCondition wait];
    return [self obtain];
    }
    }

    /**
    释放占用的资源
    */
    -(void)finish {
    @synchronized(self.mCondition) {
    self.mOccupiedThread = nil;
    [self.mCondition signal];
    }
    }

    @end

    以上就是关于for循环的异步任务处理

  • 相关阅读:
    高性能JavaScript DOM编程
    浏览器缓存机制浅析
    高性能JavaScript 循环语句和流程控制
    高性能JavaScript 编程实践
    HTML5 postMessage 跨域交换数据
    纠结的连等赋值
    从setTimeout谈JavaScript运行机制
    闭包拾遗 & 垃圾回收机制
    闭包初窥
    Odoo中如何复制有唯一性约束的记录?
  • 原文地址:https://www.cnblogs.com/-yun/p/14297132.html
Copyright © 2011-2022 走看看