iOS异步Completion Block未调用:GCD场景下复现代码求助
主队列串行特性导致Completion Block无法执行的问题分析与解决
问题本质
主队列是串行队列,所有提交到主队列的任务会严格按顺序执行,同一时间只能有一个任务在主队列线程上运行。
代码执行流程拆解
viewDidLoad本身在主队列执行,调用dispatch_async(dispatch_get_main_queue(), ...)会把[self runMyCode]这个任务追加到主队列的任务列表末尾。viewDidLoad执行完成后,主队列开始处理这个追加的任务,进入runMyCode方法。runMyCode中调用runSomeAsyncAPI,这类异步API的Completion Block通常默认会提交到主队列执行(尤其是涉及UI操作的API)。- 此时主队列的任务队列里,当前正在运行的是
runMyCode中的while循环——这个循环不断调用runMode:beforeDate:让RunLoop处理事件,但本质上它一直占用着主队列线程,主队列无法切换到下一个任务(也就是那个未执行的Completion Block)。 - 而不使用
dispatch_async时,runMyCode直接在viewDidLoad的主队列任务中执行,runSomeAsyncAPI的Completion Block会被插入到当前RunLoop的事件队列中,在while循环的runMode调用过程中被处理,因此能正常触发completed = YES,跳出循环。
解决方法
方案1:用异步回调替代阻塞等待(推荐)
彻底摒弃阻塞式的while循环,改用Completion Block链式处理逻辑:
- (void)runMyCodeWithCompletion:(void(^)(void))completion { [self runSomeAsyncAPI:^{ if (completion) { completion(); } }]; } - (void)viewDidLoad { [super viewDidLoad]; [self runMyCodeWithCompletion:^{ // 在这里处理异步任务完成后的逻辑 }]; }
方案2:将阻塞逻辑移到后台队列
如果必须等待异步任务完成,不要在主队列阻塞,切换到全局队列等待,避免占用主队列线程:
- (void)runMyCode { dispatch_group_t taskGroup = dispatch_group_create(); dispatch_group_enter(taskGroup); [self runSomeAsyncAPI:^{ dispatch_group_leave(taskGroup); }]; // 切换到后台队列等待,不阻塞主队列 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ dispatch_group_wait(taskGroup, DISPATCH_TIME_FOREVER); // 异步任务完成,处理后续逻辑 // 如果需要更新UI,切回主队列 dispatch_async(dispatch_get_main_queue(), ^{ // UI更新操作 }); }); } - (void)viewDidLoad { [super viewDidLoad]; [self runMyCode]; }
关键提示
永远不要在主队列上执行阻塞式操作(比如无限循环、长时间等待),主队列需要保持响应以处理UI渲染、用户交互等核心任务,阻塞主队列不仅会导致其他任务无法执行,还会造成界面卡顿、APP无响应(ANR)。
内容的提问来源于stack exchange,提问作者OMGPOP
相关产品推荐
相关产品推荐

