一、简介
今天是《Net 高级调试》的第十五篇文章,这个系列的文章也快结束了,但是我们深入学习的脚步还不能停止。上一篇文件我们介绍了C# 中一些锁的实现逻辑,并做到了眼见为实的演示给大家它们底层是如何实现的,今天这篇文件就主要介绍一些如何查找和解决在项目调试中遇到的锁的问题,比如:死锁、孤立锁、线程中止和终结期挂起,我们会看到表象是什么,也会做到遇到这样问题,我们如何解决问题,我们每一个操作都能做到有的放矢。我们学了锁的实现,现在又要学习有关锁的解决办法,就是让我们做到知其一,也要知其二,这些是 Net 框架的底层,了解更深,对于我们调试更有利。当然了,第一次看视频或者看书,是很迷糊的,不知道如何操作,还是那句老话,一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。
如果在没有说明的情况下,所有代码的测试环境都是 Net Framewok 4.8,但是,有时候为了查看源码,可能需要使用 Net Core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。
调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:Windows Professional 10
调试工具:Windbg Preview(可以去Microsoft Store 去下载)
开发工具:Visual Studio 2022
Net 版本:Net Framework 4.8
CoreCLR源码:源码下载
二、基础知识
在 C# 编程中会经常使用到 lock 锁,其实就是 Monitor 的语法糖,如果使用不好,经常会出现锁问题,经典的有:死锁、孤儿锁、线程中止和异常。这篇文章主要针对:死锁、孤儿锁和线程中止做介绍。
1、死锁
开中最长遇到的就是死锁,在没有【!dlk】(这个命令是 SOSEX.dll 功能,不是SOS.dll 功能,可能很多人会问,既然有这个命令,我们直接使用这个命令不就可以了吗,其实不然,dlk 包含在 SOSEX.dll 中,但是 SOSEX.dll只适合在 Net Framework 框架中使用,如果在 Net 5.0、6.0、7.0或者更高的版本是使用不了的)命令的加持下想解决问题还是有点困难的,但是手工分析和调试也是一个非常重要的基本功,也是十分考究C# 基本功的能力。
思路如下:
a、观察同步块表
b、切换到锁线程,查看 clr!AwareLock-Enter+0x4a 在等待什么对象。
2、孤儿锁(异常)
孤儿锁是因为开发者使用 Monitor.Enter 获取一个对象后,因为某种原因没有正确调用 Monitor.Exit,导致这个对象一直处于占用状态,其他线程也就无法进入了,强烈建议使用 lock 语法。
3、线程的销毁
线程销毁导致的 lock 锁未释放,寻找起来难度也很大,这种场景经常出现在和(非托管代码)交互的场景下,所以开发界限要明确,责任要清楚,代码做到高内聚低耦合,才会更安全。
三、源码调试
废话不多说,这一节是具体的调试过程,又可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。
1、调试源码
1.1、Example_15_1_1
View Code
1.2、Example_15_1_2
View Code
1.3、Example_15_1_3
View Code