今天在開發過程中發現.在SaveChanges的時候偶爾會拋出異常:Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependen ...
今天在開發過程中發現.在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環境.認為很簡單不會出問題的.做事還是太浮躁.
所以寫一篇博客,用以自省