NSURLSessionDataTask配合dispatch_semaphore等待时任务阻塞不执行问题
问题根本原因
这是典型的队列死锁,核心逻辑链如下:
- 你初始化
NSURLSession时指定delegateQueue为[NSOperationQueue mainQueue],网络任务的启动调度、请求完成后的回调派发,全都会被提交到主队列,等待主线程空闲时才能执行。 - 你在主线程调用
dispatch_semaphore_wait会直接阻塞主线程,主队列中排队的所有任务(包括网络请求的发送逻辑、回调里的dispatch_semaphore_signal)都得不到执行机会,信号量永远等不到触发信号,就会永久卡住。
修复方案
方案1(不推荐,仅临时兼容现有同步逻辑)
如果必须保留信号量等待的写法,需要做两个必要修改:
- 给NSURLSession分配独立的非主队列,不要依赖主队列派发任务和回调
- 所有包含
dispatch_semaphore_wait的请求逻辑必须放在非主线程执行,绝对不能阻塞主线程
修正后的核心代码:
// 给session创建独立工作队列,禁止使用mainQueue NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSOperationQueue *sessionWorkQueue = [[NSOperationQueue alloc] init]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:sessionWorkQueue]; // 整个请求逻辑放到全局非主线程执行 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block bool result = false; NSMutableURLRequest *request = [NSMutableURLRequest new]; request.HTTPMethod = @"POST"; NSString *urlString = @"http://localhost/api/test"; [request setURL:[NSURL URLWithString:urlString]]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; // 建议直接用NSString转NSData,避免C数组栈溢出风险 NSData *jsonBodyData = [@"reqtestmsg" dataUsingEncoding:NSUTF8StringEncoding]; [request setHTTPBody:jsonBodyData]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSHTTPURLResponse *asHTTPResponse = (NSHTTPURLResponse *) response; NSLog(@"curl The response is: %@", asHTTPResponse); if (asHTTPResponse.statusCode == 200) { printf ("curl status 200 ok\n"); result = true; } dispatch_semaphore_signal(semaphore); }]; [task resume]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 拿到结果后如果要更新UI,必须切回主线程 dispatch_async(dispatch_get_main_queue(), ^{ // 处理result、更新UI逻辑 }); });
方案2(推荐,符合框架设计逻辑)
NSURLSession本身就是为异步执行设计的,不要用信号量强行把异步请求转成同步,直接把结果处理逻辑放到请求的completionHandler回调里即可,从根源上消除线程阻塞、死锁的风险,也不会卡住UI响应。
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; // 日常使用直接用主队列也没问题,因为不会阻塞主线程 NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]]; NSMutableURLRequest *request = [NSMutableURLRequest new]; // 省略request配置逻辑,和原有代码一致 NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSHTTPURLResponse *asHTTPResponse = (NSHTTPURLResponse *) response; NSLog(@"curl The response is: %@", asHTTPResponse); bool result = false; if (asHTTPResponse.statusCode == 200) { printf ("curl status 200 ok\n"); result = true; } // 所有需要拿到result执行的逻辑直接写在这里 // 如果是UI操作,确认当前在主线程执行即可 }]; [task resume]; // 不需要加任何等待逻辑,请求完成后回调会自动触发
额外提醒:原有代码中
char json_string[20]的写法存在栈溢出风险,如果后续请求体长度超过19字节(需要预留C字符串结束符位置)会直接内存越界,建议直接通过NSString的dataUsingEncoding:方法生成请求体,不要手动操作C字符数组。
内容的提问来源于stack exchange,提问作者SongpaGu
相关产品推荐
相关产品推荐

