iOS开发:如何在不阻塞主线程时解决GCD异步任务的竞态条件?
问题拆解与解决方案
首先得说,你当前的代码有两个核心问题,导致完全达不到你想要的效果:
- 异步任务没等就返回了:
DispatchAsync是把任务丢到后台线程异步执行的,所以ShouldPerformSegue会立刻返回初始值false,根本没等后台的耗时操作完成。 - 线程安全风险:直接在后台线程修改
isAllowed变量,主线程和后台线程同时访问这个变量,很容易出现竞态条件,导致不可预期的结果。
而且ShouldPerformSegue本身是个同步方法——它必须立刻返回结果,没法在这里原地等待异步任务完成(那样会直接阻塞主线程,导致应用冻结,正好是你想避免的情况)。
正确的实现思路
咱们换个思路:先不让系统自动执行segue(返回false),等后台异步任务完成后,再手动触发segue跳转。这样既不会阻塞主线程,又能保证任务完成后再执行跳转,还能彻底避免竞态条件。
修改后的代码
public override bool ShouldPerformSegue(string segueIdentifier, NSObject sender) { // 先阻止系统默认的segue触发 ActivityIndicator.StartAnimating(); // 把耗时任务丢到后台线程执行 DispatchQueue.GetGlobalQueue(DispatchQueuePriority.High).DispatchAsync(()=> { // 这里替换成你的实际耗时业务逻辑 NSThread.SleepFor(2); // 任务完成后必须切回主线程(UI操作和segue触发都要在主线程) DispatchQueue.MainQueue.DispatchAsync(()=> { ActivityIndicator.StopAnimating(); // 手动触发segue跳转 PerformSegue(segueIdentifier, sender); }); }); return false; }
额外优化:防止重复触发
如果用户快速多次点击触发segue的控件,可能会同时启动多个后台任务,咱们可以加个标记变量来避免这种情况:
// 标记是否有任务在执行,默认是false private bool _isTaskInProgress = false; public override bool ShouldPerformSegue(string segueIdentifier, NSObject sender) { // 如果已经有任务在跑,直接返回false,拒绝新的触发 if (_isTaskInProgress) return false; _isTaskInProgress = true; ActivityIndicator.StartAnimating(); DispatchQueue.GetGlobalQueue(DispatchQueuePriority.High).DispatchAsync(()=> { // 模拟耗时操作 NSThread.SleepFor(2); DispatchQueue.MainQueue.DispatchAsync(()=> { // 任务完成后重置标记 _isTaskInProgress = false; ActivityIndicator.StopAnimating(); PerformSegue(segueIdentifier, sender); }); }); return false; }
补充说明
- 所有UI相关的操作(比如ActivityIndicator的启动/停止)必须在主线程执行,代码里已经处理了这一点;
- 如果你的异步任务可能失败(比如网络请求出错),可以在主线程的回调里添加错误处理逻辑——比如显示提示弹窗,不触发segue;
- 不要用
NSThread.SleepFor在实际项目里模拟耗时操作哦,这里只是为了演示,实际要替换成真正的业务逻辑(比如网络请求、本地数据处理等)。
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

