.NET MAUI中HttpClient带请求体POST时出现Stream已关闭错误
.NET MAUI Android POST请求抛出
ObjectDisposedException: Cannot access a closed Stream 问题详情
在.NET MAUI应用中,Android设备(含模拟器)上发起带请求体的POST请求时触发以下错误,Windows平台测试完全正常:
System.ObjectDisposedException: Cannot access a closed Stream. at System.IO.MemoryStream.EnsureNotClosed() at System.IO.MemoryStream.CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) at Xamarin.Android.Net.AndroidMessageHandler.WriteRequestContentToOutput(HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 458 at Xamarin.Android.Net.AndroidMessageHandler.DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 533 at Xamarin.Android.Net.AndroidMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs:line 375 at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at MyApp.Services.HttpService.PostAsync(String url, String body, String token, Dictionary`2 headers) in C:\Users\user\Documents\temp\MyApp\MyApp\Services\HttpService.cs:line 38
当前实现代码:
public async Task<string> PostAsync(string url, string body = "", string? token = "", Dictionary<string, string> headers = null) { HttpClient client = new(); try { var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = new StringContent("", Encoding.UTF8, "application/json"), }; if (headers != null) { foreach (var header in headers) { request.Headers.Add(header.Key, header.Value); } } if (token != "") request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); var result = await client.PostAsync(url, new StringContent("", Encoding.UTF8, "application/json")); //var result = await client.SendAsync(request); var test = await result.Content.ReadAsStringAsync(); if (result.StatusCode != HttpStatusCode.OK) { throw new Exception("Något gick fel"); } return await result.Content.ReadAsStringAsync(); } catch (Exception ex) { throw ex; } }
问题根源
- 重复读取HttpContent流:代码中两次调用
await result.Content.ReadAsStringAsync(),HttpContent的流在第一次读取后会被自动关闭,第二次读取时直接触发流已释放的异常。Windows平台的HttpClient Handler做了兼容处理,但Android的AndroidMessageHandler没有。 - 逻辑冗余与参数浪费:创建了
HttpRequestMessage但未实际使用,反而调用PostAsync重新创建空的StringContent,传入的body参数完全被忽略。 - HttpClient未复用:每次请求创建新的
HttpClient实例,会导致连接池资源耗尽,同时增加不必要的性能开销。
修复方案
修改后的代码:
// 全局复用HttpClient,推荐在依赖注入中注册为单例 private static readonly HttpClient _httpClient = new HttpClient(); public async Task<string> PostAsync(string url, string body = "", string? token = "", Dictionary<string, string> headers = null) { try { using var request = new HttpRequestMessage(HttpMethod.Post, url); // 正确使用传入的body参数构建请求体 request.Content = new StringContent(body, Encoding.UTF8, "application/json"); if (headers != null) { foreach (var header in headers) { // 区分请求头与内容头,避免添加失败 if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value)) { request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); } } } if (!string.IsNullOrEmpty(token)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } var result = await _httpClient.SendAsync(request); // 仅读取一次响应内容,复用变量避免流重复读取 var responseContent = await result.Content.ReadAsStringAsync(); if (!result.IsSuccessStatusCode) { throw new Exception($"请求失败: {result.StatusCode}, 响应内容: {responseContent}"); } return responseContent; } catch (Exception ex) { // 保留原始堆栈信息,避免throw ex丢失调用链 throw; } }
关键修复点:
- 复用
HttpClient实例,遵循.NET HttpClient最佳实践 - 仅读取一次响应内容,存储到变量中复用
- 正确使用传入的
body参数构建请求体 - 区分请求头与内容头的添加逻辑
- 修复错误处理方式,保留原始异常堆栈信息
内容的提问来源于stack exchange,提问作者Hickori
相关产品推荐
相关产品推荐

