事情的原由是因为一段简单的数据库链接代码引发,这段代码从语法上看,是没有任何问题;可是就是莫名其妙的报错了,这段代码极其简单,就是打开数据库链接,读取一条记录,而后当即更新到数据库中。可是,惨痛的事实证实,老司机也是会翻车的。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