iOS开发基础134-异步并行上传问题

先看一段代码,这是项目中图片上传的一部分代码。

// 开启线程组上传图片
    dispatch_group_t group = dispatch_group_create();
    [self.selectedPhotos enumerateObjectsUsingBlock:^(UIImage * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        dispatch_group_enter(group);
        [CHG_FileUpLoadNetwork uploadPicTestWithImage:obj response:^(ApiRequestStatusCode requestStatusCode, id  _Nonnull JSON) {
            if (requestStatusCode == CHGRequestOK) {
                imageUrlDic[[NSString stringWithFormat:@"%lu",(unsigned long)idx]] = JSON[@"data"][@"cdnUrl"];
                dispatch_group_leave(group);
            }else{
                [MBHudManager hide];
            }
        }];
    }];
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSArray *urlArray = [imageUrlDic allValues];
        weakSelf.photo_paths = urlArray;
        [weakSelf.feedbackAddCommand execute:nil];
    });

问题

好的,现在我们要求必须使用并行队列并进行异步上传,保证线程安全的同时,是否还有其他方式确保上传顺序不变?

解决

使用并行队列并进行异步上传的条件下,我们可以通过在并行队列中使用信号量及同步阻塞的方式来确保上传顺序,同时在完成所有上传任务后回到主线程进行后续操作。

这里有个关键点是我们要确保上传的结果按顺序存储,同时又要利用并行队列进行异步操作。

使用并行队列、信号量和计数器

以下是一个实现上传功能的例子,该例子在并行队列中异步进行图片上传,并使用信号量来控制并发数,确保上传结果的顺序性。

dispatch_queue_t uploadQueue = dispatch_queue_create("com.yourapp.uploadQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5); // 设置并发的最大数量

NSMutableArray *uploadResults = [NSMutableArray arrayWithCapacity:self.selectedPhotos.count];

for (NSInteger i = 0; i < self.selectedPhotos.count; i++) {
    [uploadResults addObject:[NSNull null]]; // 占位
}

for (NSUInteger i = 0; i < self.selectedPhotos.count; i++) {
    dispatch_group_enter(group);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    UIImage *image = self.selectedPhotos[i];
    dispatch_async(uploadQueue, ^{
        [CHG_FileUpLoadNetwork uploadPicTestWithImage:image response:^(ApiRequestStatusCode requestStatusCode, id  _Nonnull JSON) {
            @synchronized (uploadResults) {
                if (requestStatusCode == CHGRequestOK) {
                    uploadResults[i] = JSON[@"data"][@"cdnUrl"];
                } else {
                    NSLog(@"Image upload failed at index: %lu", (unsigned long)i);
                    // 处理错误情况,如果需要,你可以在这里进行其他处理
                }
            }
            dispatch_semaphore_signal(semaphore);
            dispatch_group_leave(group);
        }];
    });
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    weakSelf.photo_paths = [uploadResults copy];
    [weakSelf.feedbackAddCommand execute:nil];
});

说明:

  1. 创建一个并行队列 uploadQueue 和一个 dispatch_group 来跟踪所有上传任务。
  2. 创建一个信号量 semaphore 来控制最大并发数。
  3. 初始化 uploadResults 数组,并使用 NSNull 进行占位。
  4. 使用一个 for 循环对每张图片进行操作:
    • 每次进入 dispatch_group,并等待信号量(确保并发量)。
    • 在并行队列中异步执行上传任务。
    • 上传完成后更新 uploadResults 数组,同时释放信号量和 dispatch_group
  5. 当所有上传任务完成后,会调用 dispatch_group_notify,在主线程中进行后续处理。

通过这种方式,我们可以在并行队列中异步上传图片,同时确保上传结果按顺序存储,并在所有上传任务完成后进行后续处理。

分析

代码详解

dispatch_queue_t uploadQueue = dispatch_queue_create("com.yourapp.uploadQueue", DISPATCH_QUEUE_CONCURRENT);
  • 创建一个并行队列 uploadQueue。在这个队列中,任务可以并行执行。
dispatch_group_t group = dispatch_group_create();
  • 创建一个 dispatch_group,用于跟踪一组任务的完成状态。我们会将所有上传任务添加到这个组中,以便监控它们的完成情况。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5); // 设置并发的最大数量
  • 创建一个信号量 semaphore,并设置初始值为5。这用于控制并发的最大数量,防止过多的任务同时运行导致系统资源过度消耗。
NSMutableArray *uploadResults = [NSMutableArray arrayWithCapacity:self.selectedPhotos.count];

for (NSInteger i = 0; i < self.selectedPhotos.count; i++) {
    [uploadResults addObject:[NSNull null]]; // 占位
}
  • 初始化 uploadResults 数组,用于存储每张图片上传后的结果。数组的容量设置为选中的照片数,并使用 NSNull 进行占位。这确保了后续上传结果可以按索引顺序插入。
for (NSUInteger i = 0; i < self.selectedPhotos.count; i++) {
    dispatch_group_enter(group);
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    UIImage *image = self.selectedPhotos[i];
    dispatch_async(uploadQueue, ^{
        [CHG_FileUpLoadNetwork uploadPicTestWithImage:image response:^(ApiRequestStatusCode requestStatusCode, id  _Nonnull JSON) {
            @synchronized (uploadResults) {
                if (requestStatusCode == CHGRequestOK) {
                    uploadResults[i] = JSON[@"data"][@"cdnUrl"];
                } else {
                    NSLog(@"Image upload failed at index: %lu", (unsigned long)i);
                    // 处理错误情况,如果需要,你可以在这里进行其他处理
                }
            }
            dispatch_semaphore_signal(semaphore);
            dispatch_group_leave(group);
        }];
    });
}
  • 这里有几个步骤:
    1. 使用 dispatch_group_enter(group) 将当前循环的上传任务加入到 dispatch_group 中。
    2. 使用 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) 等待信号量,确保不会超过最大并发数。
    3. 通过 dispatch_async(uploadQueue, ^{...}) 异步在并行队列中执行上传任务。
    4. 在上传任务的回调中,通过 @synchronized (uploadResults) 确保对 uploadResults 数组 的插入操作是线程安全的。
    5. 根据上传结果更新 uploadResults,使用传入的 i 确保结果按顺序存储。
    6. 使用 dispatch_semaphore_signal(semaphore) 释放信号量,允许下一个任务进入。
    7. 通过 dispatch_group_leave(group) 标记该任务完成。
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    weakSelf.photo_paths = [uploadResults copy];
    [weakSelf.feedbackAddCommand execute:nil];
});
  • 使用 dispatch_group_notify 在所有上传任务完成后回到主线程执行后续操作。这部分代码会在所有 dispatch_group_leave(group) 调用完成后执行。

如何保证顺序

  1. uploadResults 数组:

    • 初始化时,用 NSNull 进行占位,确保数组的大小和上传的图片数量一致。
    • 通过图片在 self.selectedPhotos 中的索引 i 进行结果存储。即 uploadResults[i],保证了上传结果的顺序。
  2. 信号量 semaphoredispatch_group:

    • 信号量控制了并发的最大数量,每次上传任务开始时会等待信号量减1,结束时信号量加1,这样确保了我们不会一次性开始过多的任务。
    • dispatch_group 用于跟踪这组任务的完成状态,在所有任务完成后通过 dispatch_group_notify 通知主线程。
  3. 同步机制 @synchronized:

    • 确保在多线程环境下对 uploadResults 数组的修改是线程安全的,防止多个线程同时访问修改同一个数组对象。

通过以上设计,我们能够在并行队列中异步上传图片,同时确保每个任务按照初始顺序将结果存入 uploadResults 数组,最终在所有任务完成后回到主线程进行后续操作。

posted @   Mr.陳  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示