前言

事情的起因是由于一段简单的数据库连接代码引起,这段代码从语法上看,是没有任何问题;但是就是莫名其妙的报错了,这段代码极其简单,就是打开数据库连接,读取一条记录,然后立即更新到数据库中。但是,惨痛的事实证明,老司机也是会翻车的。

1. 异常的发生来得太突然

1.1 引起不舒适的代码片段
        [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();
}

这是一段不太标准的异步接口,可能你也这么写过, 从结构和语法上看,这段代码没有任何问题,而且正常情况下,它还能执行成功

1.2 报错信息

从报错信息中可以看出,数据库上下文对象被销毁了,是在什么时候销毁的呢,通过跟踪程序,了解到,是在 this.context.Update(topic); ,调用 Update 后执行了 DbContext.Dispose(),为了证明这点,我们重写 DbContext.Dispose() 方法,并简单的输出一句话

1.3 重写 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");
}
}
1.4 再次执行程序,查看结果

通过输出结果红色方框处可以看到,确实是在执行了 Update 以后执行了 Dispose 方法,关于这点,如果我们使用了同步方法,先 Update 再 SaveChanges ,这是没有任何问题的,理论上说,EFCore 中启用了 AutoDetectChangesEnabled,我们在上面的代码中其实无需调用 Update,直接 SaveChangesAsync 即可,也不会抛出异常,同理,如果是在同步方法中,先执行 Update 再 SaveChanges ,也是没有任何问题的

1.5 同步 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 方法

2. 问题所在

那到底是什么问题引起了程序执行的不确定性呢,答案就是 async/await,我们先来尝试改进一下最初的代码

2.1 改进后的代码
        [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 ,替换了 void

2.2 再次执行修正的程序

输出结果和 1.5 中的同步方法完全相同,至此,问题解决

3. 问题的解决方案

3.1 问题分析

为什么会发生这种问题呢,原因就是因为使用了异步方法 async/await 时,当没有值需要返回时,使用了 void 造成的,正确的做法是如果没有返回值,则返回 Task,如果有返回值,则使用 Task ;当一个异步方法内部没有返回 Task 的时候,基于任务的异步模式(TAP)并不知道异步任务的状态,当 this.context.Update 执行完成后,发现挂载在内存中的连接已经没有使用,就执行了回收;实际上,此时程序还没有执行完成,但是 TAP 并不知道,所以它不会去阻止这个回收的过程(使用标记),所以 async/await 应该成对出现,并且应该始终返回 Task 或者 Task,以确保 TAP 能够将上下文进行正确的挂载,否则,当异常发生时,TAP 无非将异常信息挂载到相应的 Task 上,亦无法跟踪其执行状态等信息

3.2 解决方案

请牢记下面的铁律

  • 3.2.1 在 EFCore 中,应当始终发挥 AutoDetectChangesEnabled 的特性,不要再更新实体的时候去调用 Update 方法
  • 3.2.2 使用 async/await 修饰方法时,应该始终返回 Task 或者 Task
  • 适当的使用同步方法,可避免异步踩坑
演示代码下载

https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.TaskThird

Asp.Net Core 轻松学-经常使用异步的你,可能需要看看这个文章的更多相关文章

  1. Asp.Net Core 轻松学系列-1阅读指引目录

    https://www.cnblogs.com/viter/p/10474091.html 目录 前言 1. 从安装到配置 2. 业务实现 3. 日志 4. 测试 5. 缓存使用 6.网络和通讯 7. ...

  2. Asp.Net Core 轻松学-使用MariaDB/MySql/PostgreSQL和支持多个上下文对象

    前言 在上一篇文章中(Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库)[https://www.cnblogs.com/viter/p/10243577.html],介 ...

  3. Asp.Net Core 轻松学-多线程之Task(补充)

    前言     在上一章 Asp.Net Core 轻松学-多线程之Task快速上手 文章中,介绍了使用Task的各种常用场景,但是感觉有部分内容还没有完善,在这里补充一下. 1. 任务的等待 在使用 ...

  4. Asp.Net Core 轻松学-利用文件监视进行快速测试开发

    前言     在进行 Asp.Net Core 应用程序开发过程中,通常的做法是先把业务代码开发完成,然后建立单元测试,最后进入本地系统集成测试:在这个过程中,程序员的大部分时间几乎都花费在开发.运行 ...

  5. 如何从40亿整数中找到不存在的一个 webservice Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库 WPF实战案例-打印 RabbitMQ与.net core(五) topic类型 与 headers类型 的Exchange

    如何从40亿整数中找到不存在的一个 前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况 ...

  6. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

  7. C# 中一些类关系的判定方法 C#中关于增强类功能的几种方式 Asp.Net Core 轻松学-多线程之取消令牌

    1.  IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口. public interface IAnimal { } public interface ID ...

  8. Asp.Net Core 轻松学-一行代码搞定文件上传 JSONHelper

    Asp.Net Core 轻松学-一行代码搞定文件上传   前言     在 Web 应用程序开发过程中,总是无法避免涉及到文件上传,这次我们来聊一聊怎么去实现一个简单方便可复用文件上传功能:通过创建 ...

  9. Asp.Net Core 轻松学系列-5利用 Swagger 自动生成接口文档

    目录 前言 结语 源码下载 前言     目前市场上主流的开发模式,几乎清一色的前后端分离方式,作为服务端开发人员,我们有义务提供给各个客户端良好的开发文档,以方便对接,减少沟通时间,提高开发效率:对 ...

随机推荐

  1. HTML学习笔记8:表单

      什么是表单?     一个网页表单可以将用户输入的数据发送到服务器进行处理.因为互联网用户使用复选框,单选按钮或文本字段填写表格,所以WebForms的形式类似文件或数据库.例如,WebForms ...

  2. httpd基础配置和虚拟主机的配置方法

    RedHat6.5  httpd实验的大概步骤 #解包 tar zxf httpd-2.2.17.tar.gz -C /usr/src#切换到目录 cd /usr/src/httpd-2.2.17/# ...

  3. vue.js常见的报错信息及其解决方法的记录

    1.Vue packages version mismatch 翻译:vue包版本匹配错误 报错样例: 报错原因:通常出现于一些依赖库的更新或者安装新的依赖库之后(可以认为npm update已经成为 ...

  4. 【转】关于Sentry

    1. Sentry介绍及使用 Sentry 是一个实时事件日志记录和汇集的平台.其专注于错误监控以及提取一切事后处理所需信息而不依赖于麻烦的用户反馈. 备注:国内有同类型的产品Fundebug,提供网 ...

  5. 网络营销行业十大看了就想吐的“滥词”

    网络营销行业在国内的互联网界已"猖獗"数年之久,它是一个让企业爱让用户恨的行业.有互联网的地方,就有网络营销的机会,有了机会就有了相关产业的存在,只不过是业大业小的问题.但是随着互 ...

  6. 字符流Reader和Writer

    1.Rader是字符输入流的父类. 2.Writer是字符输出流的父类. 3.字符流是以字符(char)为单位读取数据的,一次处理一个unicod. 4.字符类的底层仍然是基本的字节流. 5.Read ...

  7. 关于js中循环遍历中顺序执行多个嵌套ajax的问题

    就是业务上需要完成一个功能,这个功能需要依次调用四个接口,后面接口的参数都依赖于前一个接口的返回值. 类似这样: var objArr = "从其他逻辑获得"; for(var n ...

  8. dubbo-admin和dubbo-monitor的安装

    一.安装dubbo-admin 去这里 http://download.csdn.net/download/u013081610/10044744 下载dubbo-admin.war 部署dubbo- ...

  9. Base64 image

    [前端攻略]:玩转图片Base64编码 什么是 base64 编码? 我不是来讲概念的,直接切入正题,图片的 base64 编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址. 这样 ...

  10. Python logger 没打出行数

    # !/user/bin/python # -*- coding: utf-8 -*- ''' subprocess : 需要在linux平台上测试 shell logging ''' import ...