事情的原由是因爲一段簡單的數據庫鏈接代碼引發,這段代碼從語法上看,是沒有任何問題;可是就是莫名其妙的報錯了,這段代碼極其簡單,就是打開數據庫鏈接,讀取一條記錄,而後當即更新到數據庫中。可是,慘痛的事實證實,老司機也是會翻車的。git
[HttpPut] public async void Put([FromBody] TopicViewModel model) { var topic = this.context.Topics.Where(f => f.Id == model.Id).FirstOrDefault(); topic.Content = model.Content; this.context.Update(topic); var affrows = await this.context.SaveChangesAsync(); }
這是一段不太標準的異步接口,可能你也這麼寫過, 從結構和語法上看,這段代碼沒有任何問題,並且正常狀況下,它還能執行成功github
從報錯信息中能夠看出,數據庫上下文對象被銷燬了,是在何時銷燬的呢,經過跟蹤程序,瞭解到,是在 this.context.Update(topic); ,調用 Update 後執行了 DbContext.Dispose(),爲了證實這點,咱們重寫 DbContext.Dispose() 方法,並簡單的輸出一句話數據庫
public class ForumContext : DbContext { public ForumContext(DbContextOptions<ForumContext> options) : base(options) { } public DbSet<Topic> Topics { get; set; } public DbSet<Post> Posts { get; set; } public override void Dispose() { base.Dispose(); Console.WriteLine("Dispose"); } }
經過輸出結果紅色方框處能夠看到,確實是在執行了 Update 之後執行了 Dispose 方法,關於這點,若是咱們使用了同步方法,先 Update 再 SaveChanges ,這是沒有任何問題的,理論上說,EFCore 中啓用了 AutoDetectChangesEnabled,咱們在上面的代碼中其實無需調用 Update,直接 SaveChangesAsync 便可,也不會拋出異常,同理,若是是在同步方法中,先執行 Update 再 SaveChanges ,也是沒有任何問題的異步
[HttpPut] public void Put([FromBody] TopicViewModel model) { var topic = this.context.Topics.Where(f => f.Id == model.Id).FirstOrDefault(); topic.Content = model.Content; this.context.Update(topic); Console.WriteLine("Updated"); var affrows = this.context.SaveChanges(); Console.WriteLine("affrows:{0}", affrows); }
從輸出結果可知,先執行了 Update,而後執行了 SaveChanges 輸出 affrows,最後執行了 Dispose 方法async
那究竟是什麼問題引發了程序執行的不肯定性呢,答案就是 async/await,咱們先來嘗試改進一下最初的代碼ide
[HttpPut] public async Task Put([FromBody] TopicViewModel model) { var topic = this.context.Topics.Where(f => f.Id == model.Id).FirstOrDefault(); topic.Content = model.Content; this.context.Update(topic); Console.WriteLine("Updated"); var affrows = await this.context.SaveChangesAsync(); Console.WriteLine("affrows:{0}", affrows); }
細心的你已經發現,這段代碼和 1.1 之中的沒有太多的不一樣,無非是增長了一些跟蹤信息,其中,最關鍵的是:增長了返回值爲:Task ,替換了 voidthis
輸出結果和 1.5 中的同步方法徹底相同,至此,問題解決code
爲何會發生這種問題呢,緣由就是由於使用了異步方法 async/await 時,當沒有值須要返回時,使用了 void 形成的,正確的作法是若是沒有返回值,則返回 Task,若是有返回值,則使用 Task
;當一個異步方法內部沒有返回 Task 的時候,基於任務的異步模式(TAP)並不知道異步任務的狀態,當 this.context.Update 執行完成後,發現掛載在內存中的鏈接已經沒有使用,就執行了回收;實際上,此時程序尚未執行完成,可是 TAP 並不知道,因此它不會去阻止這個回收的過程(使用標記),因此 async/await 應該成對出現,而且應該始終返回 Task 或者 Task 對象,以確保 TAP 可以將上下文進行正確的掛載,不然,當異常發生時,TAP 無非將異常信息掛載到相應的 Task 上,亦沒法跟蹤其執行狀態等信息
請牢記下面的鐵律blog
https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.TaskThird