对 精致码农大佬 说的 Task.Run 会存在 内存泄漏 的思考
一:背景
1. 讲故事
这段时间项目延期,加班比较厉害,博客就稍微停了停,不过还是得持续的技术输出呀! 园子里最近挺热闹的,精致码农大佬分享了三篇文章:
- 为什么要小心使用 Task.Run [https://www.cnblogs.com/willick/p/14078259.html]
- 小心使用 Task.Run 续篇 [https://www.cnblogs.com/willick/p/14100973.html]
- 小心使用 Task.Run 终篇解惑 [https://mp.weixin.qq.com/s/IMPgSsxTW0LGArfPP7rQXw]
核心代码如下:
    class Program
    {
        static void Main(string[] args)
        {
            Test();
            Console.ReadLine();
        }
        static void Test()
        {
            var myClass = new MyClass();
            myClass.Foo();
        }
    }
    public class MyClass
    {
        private int _id = 10;
        public Task Foo()
        {
            return Task.Run(() =>
            {
                Console.WriteLine($"Task.Run is executing with ID {_id}");
            });
        }
    }
大意是:
Test()方法执行完之后, myClass 本该销毁,结果发现Foo()方法引用了 _id ,导致 GC 放弃了对 myClass 的回收,从而导致内存泄漏。
如果我的理解有误,请大家帮忙指正,挺有意思,评论区也是热闹非凡,总体看下来发现还是有很多朋友对 闭包, 内存泄漏,GC 等概念的认知比较模糊,同样作为技术博主,得要蹭点热度,这篇我准备从这三个方面阐述下我的认知,然后大家再回头看一下 精致 大佬的文章。
二:对闭包的认知
1. 什么是闭包
我最早接触闭包的概念是在 js 中,关于闭包的概念,懂得人自然懂,不懂的人得要挠会头,我准备不从概念而从代码入手,帮你梳理下,先看核心代码:
    public class MyClass
    {
        private int _id = 10;
        public Task Foo()
        {
            return Task.Run(() =>
            {
                Console.WriteLine($"Task.Run is executing with ID {_id}");
            });
        }
    }
我发现很多人迷惑就迷惑在 Task.Run 委托中的 _id,因为它拿的是 MyClass 中的 _id,貌似实现了时空穿越,其实仔细想想很简单哈, Task.Run 委托中要拿 MyClass._id,就必须把 MyClass 自身的 this 指针作为参数 传递给委托,既然有了这个this,啥值还拿不出来哈??? 遗憾的是 Run 不接受任何 object 参数,所以伪代码如下:
        public Task Foo()
        {
            return Task.Run((obj) =>
            {
                var self = obj as MyClass;
                Console.WriteLine($"Task.Run is executing with ID {self._id}");
            },this);
        }
上面的代码我相信大家都看的很清楚了,有些朋友要说了,空口无凭,凭什么你说的就是对的??? 没关系,我从 windbg 让你眼见为实就好啦。。。
2. 使用 windbg 验证
想验证其实很简单,用 windbg 在这条语句 Console.WriteLine($"Task.Run is executing with ID {_id}");  上放一个断点,命中之后看一下这个方法的参数列表就好了。
这句代码在我文件的第 35 行,使用命令  !bpmd Program.cs:35 设置断点。
0:000> !bpmd Program.cs:35
0:000> g
JITTED ConsoleApp4!ConsoleApp4.MyClass.<Foo>b__1_0()
Setting breakpoint: bp 00007FF83B2C4480 [ConsoleApp4.MyClass.<Foo>b__1_0()]
Breakpoint 0 hit
00007ff8`3b2c4480 55              push    rbp
上面的 <Foo>b__1_0() 方法就是所谓的委托方法,接下来可以用 !clrstack -p 查看这个方法的参数列表。
0:009> !clrstack -p
OS Thread Id: 0x964c (9)
        Child SP               IP Call Site
000000BF6DB7EF58 00007ff83b2c4480 ConsoleApp4.MyClass.b__1_0() [E:\net5\ConsoleApp1\ConsoleApp4\Program.cs @ 34]
    PARAMETERS:
        this (<CLR reg>) = 0x0000025c26f8ac60
可以看到,这个方法有一个参数 this, 地址是: 0x0000025c26f8ac60,接下来可以用 !do 0x0000025c26f8ac60 试着打印一下,看看到底是什么?
0:009> !do 0x0000025c26f8ac60
Name:        ConsoleApp4.MyClass
MethodTable: 00007ff83b383548
EEClass:     00007ff83b3926b8
Size:        24(0x18) bytes
File:        E:\net5\ConsoleApp1\ConsoleApp4\bin\Debug\netcoreapp3.1\ConsoleApp4.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ff83b28b1f0  4000001        8         System.Int32  1 instance               10 _id
观察上面输出,哈哈,果然不出所料,0x0000025c26f8ac60 就是 ConsoleApp4.MyClass,现在对闭包是不是已经有了新的认识啦???
二:对内存泄漏的认识
1. 何为内存泄漏
英文中有一个词组叫做 Out of Control,对,就是失去控制了,要想释放只能 自杀式袭击 了, 比如说:kill 进程,关机器。
好了,再回到这个例子上来,代码如下:
        static void Test()
        {
            var myClass = new MyClass();
            myClass.Foo();
        }
当 Test 方法执行完成之后,myClass 的栈上引用地址肯定会被抹掉的, 有意思的是此时 Task.Run 中的委托方法肯定还没有得到线程调度,我又发现很多人在这一块想不通了,以为 内存泄漏 了。 对吧 												
对 精致码农大佬 说的 Task.Run 会存在 内存泄漏 的思考的更多相关文章
- 对精致码农大佬的 [理解 volatile 关键字] 文章结论的思考和寻找真相
		一:背景 1. 讲故事 昨天在园里的编辑头条看到 精致码农大佬 写的一篇题为:[C#.NET 拾遗补漏]10:理解 volatile 关键字 (https://www.cnblogs.com/will ... 
- Task.Run(), Task.Factory.StartNew() 和 New Task() 的行为不一致分析
		重现 在 .Net5 平台下,创建一个控制台程序,注意控制台程序的Main()方法如下: static async Task Main(string[] args) 方法的主体非常简单,使用Task. ... 
- 56岁潘石屹生日当天宣布要学编程语言Python,网友:地产商来抢码农饭碗了!
		最近在码农界里,一个比较轰动的事情,就是地产大佬潘石屹,在56岁生日当天宣布要学习编程语言Python. 可能部分老铁不认识潘石屹,简单介绍下大佬背景: 潘石屹,1963年11月14日出生于甘肃天水, ... 
- 6年DotNet码农的盲目经历
		前言 第一篇没有选择记录与技术相关的文档,是考虑到有必要给查阅这篇文档的伙伴们“自我介绍”一下,大佬们看了求带或指导,我很愿意学习,初学者们看了千万不要重复走我之前的“学习之路”:我老家贵州,再过 ... 
- 【整理】待毕业.Net码农就业求职储备
		声明:本文题目来源于互联网,仅供即将从学校毕业的.Net码农(当然,我本人也是菜逼一个)学习之用.当然,学习了这些题目不一定会拿到offer,但是针对就业求职做些针对性的准备也是不错的.此外,除了技术 ... 
- <开心一笑> 码农 黑客和2B程序员之间的区别
		笔记本电脑 码农: 黑客: 2B程序员: 求2的32次方: 码农: System.out.println(Math.pow(2, 32)); 黑客: System.out.println(1L< ... 
- 经典算法C++版(参考一线码农博文)
		鉴于一线码农的算法博文基本通过C#完成,此处用C++再实现一遍,具体解法可参考其博文. 地址:http://www.cnblogs.com/huangxincheng/category/401959. ... 
- [2013 eoe移动开发者大会]靳岩:从码农到极客的升级之路
		(国内知名Android开发论坛 eoe开发者社区推荐:http://www.eoeandroid.com/) 前天,2013 eoe 移动开发者大会在国家会议中心召开,eoe 开发者社区创始人靳岩在 ... 
- 专门为码农定制的14款创意的T裇(T-Shirt)设计
		T裇衫是人们在各种场合都可穿着的服装,如在T裇衫上作适当的装饰,即可增添无穷的韵味.通过图案直接反映人类的精神风貌,你可以把日常生活中的兴趣.习惯.喜怒哀乐.嗜好等展露无疑,张扬个性.秀出自我.对于码 ... 
随机推荐
- python_选课系统
			import sys import pickle import os USERINFO = r'C:\Users\12078\PycharmProjects\OldBoy\选课系统\userinfo' ... 
- python执行rados命令例子
			前言 我们以前的管理平台在python平台下面做的,内部做的一些操作采用的是命令执行,然后解析的方式去做的,ceph自身有python的rados接口,可以直接调用原生接口,然后直接解析json的方式 ... 
- 利用移动硬盘安装windows7系统
			首先把win7系统镜像的iso文件解压到移动硬盘中 将移动硬盘设置为活动分区 设置活动分区的方法 Diskpart程序实现U盘安装WIN7的方法: 将Win7安装盘中的所有文件拷贝到硬盘文件夹中,我们 ... 
- JVM初识
			先来看一张图 首先jvm是什么? jvm是java运行环境的一部分,是一种以软件模式虚拟出来的一个计算机系统. 如上图所示,JVM 主要分为三个子系统:类加载器.运行时数据区和执行引擎. 类加载器子系 ... 
- SQL Server 数据库bak备份文件还原操作和mdf文件附加操作
			前言:现在任何软件都离不开数据的支持,数据的价值是无价的,因此数据目前显得尤为重要,日常软件生产库的数据定时或实时备份必不可少,备份出的文件也需要进行验证,下边我将介绍SQL Server数据的的备份 ... 
- Docker学习第三天
			1.配置阿里容器镜像加速器 (1)登录阿里云官网搜索"镜像加速器" (2)添加如上图信息进行配置 (3)重新加载deamon和重启docker服务 sudo systemctl d ... 
- MSSQL渗透测试
			mssql-getshell 来源:独自等待,知乎,github xp_cmdshell 第一种:在SQL Server 2005之前版本中,xp_cmdshell是默认开启的,因此可以直接利用,执行 ... 
- windows下mysql的远程访问和权限设置
			如果想要用户root可以远程登录,则可通过修改user表中root用户对应的host字段值为"%"即可.我们用以下语句进行修改: update user set host = '% ... 
- java8新特性LocalDate、LocalTime、LocalDateTime的学习
			以前操作时间都是使用SimpleDateFormat类改变Date的时间格式,使用Calendar类操作时间.但是SimpleDateFormat是线程不安全的,源码如下: private Strin ... 
- Stream流的这些操作,你得知道,对你工作有很大帮助
			Stream流 Stream(流)是一个来自数据源的元素队列并支持聚合操作: 元素是特定类型的对象,形成一个队列. Java中的Stream并不会存储元素,而 是按需计算. 数据源 流的来源. 可以是 ... 
