1. 程式人生 > >記一次EF Core DBContext在Action委託中GC異常的問題.

記一次EF Core DBContext在Action委託中GC異常的問題.

今天在開發過程中發現.在SaveChanges的時候偶爾會丟擲異常:Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

異常說得很明顯,通過依賴注入的DBContext上下文已經被其他地方Dispose了.所以無法再次Dispose.

 

 

 

 

 

 

程式碼邏輯很簡單,就是傳送郵件後,呼叫委託通知tracking傳送成功.然後儲存到資料庫.

按理說這段程式碼沒問題,但就是報錯了.

一開始以為是哪裡沒有await,所以導致task開的新執行緒沒有被等待,從而導致提前gc.但程式碼就這麼多,都檢查過了沒有遺漏的地方.排除

後來懷疑是DBContext的生命週期問題,但DBcontext是ServiceLifetime.Scoped.  同一個request中是單例的.排除

後來經過和同事交流,在程式碼結尾處加入Task.CompletedTask.等待所有執行緒結束.

 

神奇的事情發生了,完美執行.不報錯了.

那這樣的話,就問題就只可能是定義的Action<int> rollBack委託的問題了.

 

 

 

 

 

 猛然發現,儘管我在程式碼中確實加入了async/await關鍵字

 

 

 但是這裡的非同步等待,只是非同步等待委託內部的操作.並不等待Action委託本身.也就是說,當我們執行委託裡的方法時.開闢了一個新的執行緒去執行_dbContext.SaveChangesAsync()的方法.但是並沒有等待它完成.

這時候主執行緒會立即執行下一步,也就是返回結果給Controller層.  Return Ok()給前端.這個時候DBContex立刻就會呼叫Dispose.等到委託的方法呼叫完畢再次Dispose的時候.自然而然的就會丟擲異常啦.因為他之前已經被Dispose了.

所有解決辦法很簡單

方法一 在程式碼結尾加入await Task.CompletedTask  等待所有執行緒都結束.再返回.

方法二 講Action<int> 換成 Func<int,Task> 並在呼叫委託前 await 

 

經過這次問題,還是暴露出不少問題.

1:對async/await 還是有認識不足的地方.基礎知識不紮實,導致了對委託的錯誤使用.

2:對自己的程式碼太過自信.沒有做完整的測試.事實上 這裡的程式碼我都沒測試過就上了DEV環境.認為很簡單不會出問題的.做事還是太浮躁.

所以寫一篇部落格,用以自省

&n