文章目錄
使用 HttpClient 出現 ObjectDisposedException ?
最近某個專案中有個需求需要對 partner 發出 http request,而 user 針對 request 出現 error 時希望加上 retry 機制:重試一次
,結果就是這個重試一次的要求讓程式出現預期外的 Exception,立馬來看看我犯了什麼錯吧
程式碼
模擬用 api
不會回傳
ok
public class ValuesController : ApiController { // POST api/values public string Post([FromBody]UserModel model) { return $"Name:{model.Name};Birthday:{model.BOD};Salary:{model.Salary}"; } } public class UserModel { public string Name { get; set; } public DateTime BOD { get; set; } public int Salary { get; set; } }
實際呼叫端
async Task Main() { var submitUrl="http://localhost:33173/api/Values"; //建立 HttpClient using (HttpClient client = new HttpClient()) { // 準備送出的 data var postData = new UserModel { Name = Guid.NewGuid().ToString(), BOD = DateTime.Now, Salary = 100 }; // 將 data 轉為 json string json = JsonConvert.SerializeObject(postData); // 將轉為 string 的 json 依編碼並指定 content type 存為 httpcontent HttpContent contentPost = new StringContent(json, Encoding.UTF8, "application/json"); // 發出 post 並取得結果 HttpResponseMessage response = await client.PostAsync(submitUrl, contentPost); // 將回應結果內容取出並轉為 string var result = await response.Content.ReadAsStringAsync(); //回應內容未包含 "ok" 即重送 if (!result.Contains("ok")) { // retry response = await client.PostAsync(submitUrl, contentPost); //將結果輸出 $"second response:{response}".Dump(); } } } public class UserModel { public string Name { get; set; } public DateTime BOD { get; set; } public int Salary { get; set; } }
錯誤訊息
訊息內容
Data Data HelpLink null HResult -2146233088 InnerException System.ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Http.StringContent'. at System.Net.Http.HttpContent.CheckDisposed() at System.Net.Http.HttpContent.CopyToAsync(Stream stream, TransportContext context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) InnerExceptions ReadOnlyCollection`1 Message One or more errors occurred. Source mscorlib StackTrace at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) at System.Threading.Tasks.Task`1.get_Result() at UserQuery.Main() in C:\Users\yowko.tsai\AppData\Local\Temp\LINQPad5\_qjoirzcd\query_semfzk.cs:line 56 at LINQPad.ExecutionModel.ClrQueryRunner.Run() at LINQPad.ExecutionModel.Server.RunQuery(QueryRunner runner) at LINQPad.ExecutionModel.Server.StartQuery(QueryRunner runner) at LINQPad.ExecutionModel.Server.<>c__DisplayClass153_0.<ExecuteClrQuery>b__0() at LINQPad.ExecutionModel.Server.SingleThreadExecuter.Work() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() TargetSite TargetSite
錯誤截圖
發生原因解析
由 StackTrace 得知出現問題的來源:
System.Net.Http.HttpContent.CheckDisposed()
針對
HttpContent.cs
原始碼偵錯請參考 HttpContent.cs
CheckDisposed()
方法位於 ln.457disposed
屬性於 ln.435 被修改
找出 HttpContent 何時被 disposed
請參考 HttpClient.cs
程式呼叫 ln.389 的
PostAsync()
轉 call ln.256 的
SendAsync
http request 完成後 call
DisposeRequestContent
DisposeRequestContent
中 dispose httpcontent
心得
過去如果需要在 http request 中加入 retry 機制,都是透過迴圈或是遞迴方式來達成,而此次 user 明確指出只需 retry once,讓我偷懶直接重新 post,而未加入過去慣用的 retry 機制,因此剛好有這個機會瞭解到原來 HttpClient 為了確保 request content 只會被發送一次,會直接 dispose request content
透過此次問題偵錯的過程深深地感受到自己還有好多開發基礎知識掌握度不夠,也發覺過去的成功經驗可能會因自己偷懶未完成複製而成為失敗的主因呀
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-02
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。