其他应用已加载程序集时,反射操作变慢的原因及解决办法
反射加载DLL遇性能瓶颈?原因和解决办法看这里
为啥别的应用加载过相同DLL后,你的流程变慢了?
这事儿大概率和Windows文件锁定机制以及**.NET程序集加载的共享行为**脱不了干系。当另一个应用已经把这些DLL加载进内存时,系统会对这些文件加上共享锁定——意思是其他进程只能读取、不能修改,且读取操作可能因为文件被占用导致IO等待大幅变长。你的应用在执行反射加载流程时,需要读取DLL内容来解析程序集信息、类型定义,这时候每一步IO操作都会因为文件被其他进程占用而变慢,最终从20秒拖到2分钟。
还有一种可能是影子复制的影响:如果别的应用或者你的应用开启了程序集影子复制,当原文件被锁定时,.NET会自动把DLL复制到临时目录再加载,这个复制加等待的过程也会显著拉长执行时间。
给你几个落地的解决办法
1. 先复制DLL到专属临时目录再加载(最推荐)
既然原目录的DLL容易被其他进程占用,那咱们先把需要的DLL复制到一个仅当前应用可用的临时文件夹,再从这个文件夹加载。这样能彻底避开文件锁定冲突,性能立马回升。
代码示例:
// 源DLL目录 string sourceDllDir = @"C:\YourTargetDllFolder"; // 生成唯一临时目录,避免和其他应用实例冲突 string tempLoadDir = Path.Combine(Path.GetTempPath(), $"MyApp_LoadCache_{Guid.NewGuid()}"); Directory.CreateDirectory(tempLoadDir); // 复制所有DLL到临时目录 foreach (var dllPath in Directory.GetFiles(sourceDllDir, "*.dll")) { string destPath = Path.Combine(tempLoadDir, Path.GetFileName(dllPath)); // 覆盖已有文件,确保拿到最新版本 File.Copy(dllPath, destPath, overwrite: true); } // 从临时目录加载程序集,后续筛选、实例化逻辑和你原有代码一致 foreach (var tempDllPath in Directory.GetFiles(tempLoadDir, "*.dll")) { Assembly assembly = Assembly.LoadFrom(tempDllPath); // 按AssemblyInfo属性筛选程序集... // 检查类型是否实现IAssemblyInitializer... // 实例化并执行InitializeAssembly方法... } // 应用退出时主动清理临时目录(可选,系统会自动清理临时文件,但主动清理更友好) AppDomain.CurrentDomain.ProcessExit += (sender, e) => { try { Directory.Delete(tempLoadDir, recursive: true); } catch { /* 忽略清理失败情况,比如文件仍被占用 */ } };
2. 开启程序集影子复制
.NET自带的影子复制功能,会自动把要加载的程序集复制到临时目录,再从临时目录加载,这样原文件就不会被锁定。你需要在应用启动初期给当前AppDomain配置这个功能:
// 务必在Main方法开头配置,当前AppDomain启动后无法修改该设置 var setup = AppDomain.CurrentDomain.SetupInformation; setup.ShadowCopyFiles = "true"; // 可选:指定影子复制的缓存目录,默认使用系统临时文件夹 setup.CachePath = Path.Combine(Path.GetTempPath(), "MyApp_ShadowCache"); // 如果需要创建独立AppDomain加载程序集,可使用该配置创建新域 // AppDomain newLoadDomain = AppDomain.CreateDomain("MyAssemblyLoadDomain", null, setup);
3. 优化反射逻辑,减少不必要的开销
除了文件锁定的问题,你的反射代码也可以做些优化来提速:
- 先筛程序集,再查类型:先根据AssemblyInfo的特定属性过滤掉不需要的程序集,再调用
assembly.GetTypes()——因为GetTypes()本身是比较耗时的操作,少查几个程序集就能省不少时间。 - 缓存检查结果:如果应用会多次执行加载流程,把已经检查过的程序集、符合条件的类型缓存起来,下次直接复用,不用重复执行筛选逻辑。
- 用更高效的类型检查方式:把
type.GetInterface(typeof(IAssemblyInitializer).FullName) == null替换成typeof(IAssemblyInitializer).IsAssignableFrom(type),这个方法的性能更优。
优化后的类型检查代码:
Type targetInterface = typeof(IAssemblyInitializer); foreach (var assembly in filteredAssemblies) // filteredAssemblies是已按AssemblyInfo筛选过的程序集 { foreach (var type in assembly.GetTypes()) { // 先检查是否实现目标接口 if (!targetInterface.IsAssignableFrom(type)) continue; // 排除抽象类、非类类型 if (type.IsAbstract || !type.IsClass) continue; // 检查是否存在无参构造函数 if (!type.GetConstructors(BindingFlags.Public | BindingFlags.Instance) .Any(c => c.GetParameters().Length == 0)) continue; // 实例化并执行方法 var initializer = Activator.CreateInstance(type) as IAssemblyInitializer; initializer?.InitializeAssembly(); } }
4. 排查依赖DLL的加载问题
有时候性能瓶颈不是主DLL的问题,而是它依赖的其他DLL被锁定了。确保所有依赖的DLL也能正常访问,或者同样复制到临时目录,避免加载依赖时出现IO等待。
内容的提问来源于stack exchange,提问作者J4N
相关产品推荐
相关产品推荐

