使用HttpClient调用REST API失败:旧代码可用新代码崩溃
问题:异步HttpClient调用Jira API时无提示崩溃
一、可正常工作的旧函数(基于HttpWebRequest)
调用方式
string requestData = RestClient.JiraRequest(buildFilter());
函数实现
public static string JiraRequest(string api) { string data = string.Empty; try { string requestUrl = @"https://jira.xxxxx.org/rest/api/2/" + api; string authType = "Basic"; string httpMethod = "GET"; string authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(Config.UserName + ":" + Config.UserPassword)); _ = Encoding.UTF8.GetBytes(authHeader); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl); request.Method = httpMethod; request.Headers.Add("Authorization", authType + " " + authHeader); request.ContentType = "application/json"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); if (response.StatusCode != HttpStatusCode.OK) { throw new ApplicationException("Exception error: " + response.StatusCode.ToString()); } using (Stream responseStream = response.GetResponseStream()) { if (responseStream != null) { using (StreamReader reader = new StreamReader(responseStream)) { data = reader.ReadToEnd(); } } } } catch (Exception ex) { HandleExeption.Exception(ex); return data; } return data; }
二、调用后崩溃的新异步函数(基于HttpClient)
调用方式
string requestData = await RestClient.JiraRequest(buildFilter());
函数实现
public static async Task<string> JiraRequest(string api) { string data = string.Empty; try { string requestUrl = @"https://jira.xxxxx.org/rest/api/2/" + api; string authType = "Basic"; using (HttpClient client = new()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authType, Convert.ToBase64String(Encoding.ASCII.GetBytes(Config.UserName + ":" + Config.UserPassword))); HttpResponseMessage responseMessage = await client.GetAsync(requestUrl); if (!responseMessage.IsSuccessStatusCode) { throw new ApplicationException("HttpClient exception: " + responseMessage.StatusCode.ToString()); } data = await responseMessage.Content.ReadAsStringAsync(); return data; } } catch (Exception ex) { HandleExeption.Exception(ex); return data; } }
三、问题描述
旧代码基于HttpWebRequest实现,因收到该API已过时的提示,改用HttpClient开发异步调用函数。旧函数可正常调用Jira REST API,但新函数执行到HttpResponseMessage responseMessage = await client.GetAsync(requestUrl);时,程序直接崩溃退出,无异常信息输出,使用Debug.WriteLine也无法捕获日志。
四、异常处理函数实现
public static void Exception(Exception ex) { StackTrace stackTrace = new(ex, true); #nullable enable StackFrame? stackFrame = stackTrace.GetFrame(stackTrace.FrameCount - 1); #nullable disable Debug.WriteLine($"Exception message: {ex.Message ?? "No message"}"); if (stackFrame != null) { var method = stackFrame.GetMethod(); Debug.WriteLine($"Exception in method: {method}"); var lineNumber = stackFrame.GetFileLineNumber(); Debug.WriteLine($"Exception in line: {lineNumber}"); } }
问题排查与解决方案
1. 异步调用未正确等待导致主线程退出
这是无提示崩溃最常见的原因:如果调用await RestClient.JiraRequest的方法是同步方法(比如控制台程序的Main未标记async),直接使用await会导致异步操作未完成时主线程提前退出,程序直接终止,不会触发任何异常捕获。
解决办法:
- 控制台程序将
Main改为异步:static async Task Main(string[] args) { string requestData = await RestClient.JiraRequest(buildFilter()); // 后续逻辑 } - 无法修改调用方法为异步时,用
.GetAwaiter().GetResult()替代await(注意:非UI/非ASP.NET环境下谨慎使用,避免死锁):string requestData = RestClient.JiraRequest(buildFilter()).GetAwaiter().GetResult();
2. HttpClient实例滥用导致资源问题
HttpClient设计为可复用对象,每次请求创建新实例可能导致套接字耗尽;同时using包裹实例在同步等待场景下可能提前释放对象。
优化方案:改为静态单例模式
private static readonly HttpClient _httpClient = new HttpClient(); public static async Task<string> JiraRequest(string api) { string data = string.Empty; try { string requestUrl = @"https://jira.xxxxx.org/rest/api/2/" + api; string authType = "Basic"; // 仅首次调用时设置授权头,避免重复添加 if (!_httpClient.DefaultRequestHeaders.Contains("Authorization")) { _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authType, Convert.ToBase64String(Encoding.ASCII.GetBytes(Config.UserName + ":" + Config.UserPassword))); } HttpResponseMessage responseMessage = await _httpClient.GetAsync(requestUrl); if (!responseMessage.IsSuccessStatusCode) { throw new ApplicationException("HttpClient exception: " + responseMessage.StatusCode.ToString()); } data = await responseMessage.Content.ReadAsStringAsync(); return data; } catch (Exception ex) { HandleExeption.Exception(ex); return data; } }
3. 未捕获全局异步异常
异步操作的异常可能抛到全局上下文,未被当前try/catch捕获,需添加全局异常处理:
- 控制台程序:
AppDomain.CurrentDomain.UnhandledException += (sender, e) => { var ex = e.ExceptionObject as Exception; Debug.WriteLine($"未处理异常: {ex?.Message ?? "未知错误"}"); Debug.WriteLine($"堆栈跟踪: {ex?.StackTrace}"); }; TaskScheduler.UnobservedTaskException += (sender, e) => { Debug.WriteLine($"未观察到的任务异常: {e.Exception.Message}"); Debug.WriteLine($"堆栈跟踪: {e.Exception.StackTrace}"); e.SetObserved(); }; - WPF/WinForms:分别添加
Application.DispatcherUnhandledException或Application.ThreadException事件处理。
4. 验证授权头正确性
虽然新旧代码逻辑一致,可打印授权头确认是否与旧代码生成的一致,排除配置读取问题:
var credentials = $"{Config.UserName}:{Config.UserPassword}"; var base64Credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(credentials)); Debug.WriteLine($"Base64授权字符串: {base64Credentials}");
内容的提问来源于stack exchange,提问作者Edlaos
相关产品推荐
相关产品推荐

