欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

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

前言

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

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 报错信息

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

从报错信息中可以看出,数据库上下文对象被销毁了,是在什么时候销毁的呢,通过跟踪程序,了解到,是在 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 再次执行程序,查看结果

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

通过输出结果红色方框处可以看到,确实是在执行了 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);
        }
  • 输出结果

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

从输出结果可知,先执行了 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 再次执行修正的程序

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

输出结果和 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