C#接口请求代码功能及Stopwatch、async/await用法与死锁问题咨询
代码功能说明
这段是C#的泛型工具方法,实现的逻辑是:
- 新建HttpClient实例,添加Authorization请求头,向指定URI发起HTTP GET请求
- 全程统计请求耗时,拿到响应后读取响应内容为字符串
- 校验响应状态码为成功的前提下,将XML格式的响应内容反序列化为指定的泛型类型T实例后返回
注意原代码存在空吞异常、每次新建HttpClient易引发端口耗尽、同步阻塞异步方法易引发死锁等问题。
核心类作用说明
- Stopwatch:.NET提供的高精度计时器,这里用来统计从发起HTTP请求到读取完响应内容的全链路耗时,可用于性能监控、接口耗时统计等场景。
- XmlSerializer:.NET内置的XML序列化/反序列化组件,这里的作用是将接口返回的XML格式字符串,转换为开发者指定的泛型类型T的强类型实体对象,无需手动解析XML节点。
- MemoryStream:基于内存的流实现,因为XmlSerializer的反序列化方法只接收流类型入参,这里是将XML字符串转成UTF8字节数组后封装为内存流,不需要写磁盘,性能更高,用完会自动释放。
async/await 优化实现
完全可以用async/await改写,改写后既能避免同步阻塞问题,代码也更简洁易读,优化后代码如下:
// 异步方法约定返回Task<T>,方法名加Async后缀 public async Task<T> GetResponseAsync<T>() { var response = default(T); // 注意:生产环境建议复用HttpClient实例或通过IHttpClientFactory注入,避免端口耗尽问题 using (HttpClient httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Add("Authorization", authHeader); Stopwatch watch = Stopwatch.StartNew(); // 用await代替Wait()和Result,无需手动阻塞线程 var wsResponseContent = await httpClient.GetAsync(uri.ToString()); await wsResponseContent.Content.LoadIntoBufferAsync(); string wsResponseContentText = await wsResponseContent.Content.ReadAsStringAsync(); if (watch.IsRunning) watch.Stop(); wsResponseContent.EnsureSuccessStatusCode(); if (wsResponseContent.IsSuccessStatusCode) { XmlSerializer serializer = new XmlSerializer(typeof(T)); using (MemoryStream sresultStream = new MemoryStream(Encoding.UTF8.GetBytes(wsResponseContentText))) { response = (T)serializer.Deserialize(sresultStream); // using语句会自动释放流,无需手动调用Close() } } } return response; }
关于contentResponse.Result死锁的问题
调用contentResponse.Result并不一定会导致死锁,死锁的发生需要满足两个前提:
- 当前运行环境存在同步上下文(SynchronizationContext),比如WinForm/WPF的UI线程、http://ASP.NET Framework的请求处理线程都自带同步上下文;
- 调用
.Result/.Wait()的线程阻塞了同步上下文,而异步方法执行完成后需要回到原同步上下文执行后续逻辑,两者互相等待就会触发死锁。
如果是在没有同步上下文的环境,比如控制台程序、http://ASP.NET Core应用中,默认没有同步上下文,调用.Result通常不会死锁,但仍然不推荐这种写法:同步阻塞会浪费线程资源,高并发场景下容易引发线程池饥饿,导致服务吞吐量下降。
内容的提问来源于stack exchange,提问作者user2281858
相关产品推荐
相关产品推荐

