.NET IIS环境静态反射缓存更新后数据过时问题咨询
IIS自动回收后静态反射缓存过期问题说明
问题背景
做反射相关逻辑开发时,通常会把检索、处理完成的反射信息存储在对应工作类的私有静态字段中做缓存,避免重复执行反射消耗性能。但在实际使用中遇到过类代码修改后,静态缓存的反射数据过期、无法反映类当前元数据的问题。在ASP.NET场景下,手动重置IIS或者回收对应应用程序池后,数据就能重新正常加载。
核心疑问
已知IIS检测到web.config或者bin目录变更时会自动回收应用程序池,为什么旧的缓存数据仍然会留存?如何在标准配置的IIS实例上复现该问题?
运行环境
- .NET运行时:.NET Framework 4.8
- Web服务器:Windows 10专业版自带的IIS 10
已观测到的问题触发流程
- 初始状态下
SomeBusinessService类标记有MySpecialAttribute特性(即标注[MySpecial]),应用在IIS上正常启动运行; - 开发人员修改
SomeBusinessService类代码,为其新增第二个特性[MySecond],随后在Visual Studio 2019中生成解决方案,生成输出路径直接指向IIS站点对应的实际运行目录,此时站点bin目录发生文件变更; - 开发人员再次访问应用时,存储反射信息的静态字段已经填充了旧值,程序仍然使用过期的反射信息,没有识别到新增的特性。
问题根因
这个现象和IIS的自动回收逻辑并不冲突,本质是ASP.NET程序集加载机制+回收流程的时间差共同导致的:
- IIS的应用池回收默认采用重叠回收模式:检测到bin目录或web.config变更后,不会立刻终止旧的工作进程(w3wp.exe),而是先启动新的工作进程承接新请求,等新进程就绪后再逐步关停旧进程。由于VS生成解决方案时是逐个往bin目录写入DLL文件,第一个DLL写入完成的瞬间就会触发变更检测启动新进程,此时新旧进程会同时存在一段时间,如果请求命中了还未退出的旧进程,自然会读到旧进程里静态字段存储的缓存数据。
- ASP.NET默认启用了程序集影子拷贝(Shadow Copy)机制:运行时不会直接锁定加载bin目录下的原始DLL,而是先把DLL复制到系统临时ASP.NET文件目录(默认路径为
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\)后再加载,目的是避免运行中bin目录文件被锁定无法更新。如果新DLL还没完全写入磁盘时新应用域就启动了,运行时可能直接复用临时目录里残留的旧版本程序集拷贝,此时静态字段初始化执行反射拿到的就是旧的元数据,哪怕是新启动的进程也会读到过期缓存。
标准配置IIS下的复现步骤
不需要修改任何IIS默认配置,按以下操作即可稳定复现:
- 新建.NET Framework 4.8版本的ASP.NET Web项目,添加两个特性类
MySpecialAttribute、MySecondAttribute,再添加SomeBusinessService类,初始只给该类添加[MySpecial]标记。 - 编写测试入口(页面或接口):在类的静态构造函数中反射扫描
SomeBusinessService的所有自定义特性,将扫描结果存入私有静态字段,请求时直接返回该静态字段存储的特性列表。 - 将站点部署到本地IIS 10,首次访问测试入口,确认返回结果仅包含
MySpecial。 - 不停止IIS站点,直接打开项目给
SomeBusinessService添加[MySecond]特性,将VS项目的生成输出路径修改为IIS站点的bin目录,点击生成解决方案。 - 生成完成后立刻连续刷新访问测试入口,会出现返回结果仅包含
MySpecial的情况,直到手动回收应用池或重启IIS后,才会返回同时包含两个特性的正确结果。
排查小技巧:可以在测试入口额外返回当前加载的
SomeBusinessService程序集的物理路径、文件最后修改时间、当前工作进程ID,出现缓存问题时可以直观看到是命中了未退出的旧w3wp进程,还是新进程加载了临时目录下的旧版本程序集。
内容的提问来源于stack exchange,提问作者SynBiotik
相关产品推荐
相关产品推荐

