Assembly Load Context下TPL.DataFlow加载异常及AssemblyResolve疑问
加载引用TPL.DataFlow库的程序集时,遇到TPL.DataFlow.dll无法加载的问题。根据微软文档,使用Assembly.LoadFrom()时,程序集所在文件夹会被纳入Assembly Load Context的考量范围。我通过AssemblyResolve事件解决了该问题,但调试时发现并未进入if (assembly == null)分支——也就是程序集已加载到上下文却未被解析,对此存在疑惑。
相关代码
public static IService LoadService(CtorParameters parameters) { AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => CurrentDomain_AssemblyResolve(sender, args, parameters.DllLocation); try { var assembly = Assembly.LoadFrom(parameters.DllLocation); var serviceType = assembly.GetType(SERVICE_TYPE); var serialPortInstance = (IService)Activator.CreateInstance(serviceType, parameters); return serialPortInstance; } finally { AppDomain.CurrentDomain.AssemblyResolve -= (sender, args) => CurrentDomain_AssemblyResolve(sender, args, parameters.DllLocation); } } private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args, string loadedAssemblyPath) { var appDomain = (AppDomain)sender; if (args.RequestingAssembly == null) { try { var assemblyName = new AssemblyName(args.Name); var assembly = appDomain.GetAssemblies().FirstOrDefault(m => m.GetName().Name == assemblyName.Name); if (assembly == null) { var loadedDllDir = Path.GetDirectoryName(loadedAssemblyPath); var dllPath = Path.Combine(loadedDllDir, $"{assemblyName}.dll"); assembly = Assembly.LoadFrom(dllPath); } return assembly; } catch { } } return args.RequestingAssembly; }
原因分析与解释
上下文不匹配导致的解析触发:
Assembly.LoadFrom()加载的程序集会被放入LoadFrom上下文,而CLR解析依赖时,优先在默认加载上下文(应用程序基目录、探测路径下的程序集)查找。即便TPL.DataFlow.dll已经在LoadFrom上下文加载,默认上下文没有匹配项时,CLR仍会触发AssemblyResolve事件。此时你的代码通过appDomain.GetAssemblies()能找到已加载的实例,自然不会进入if (assembly == null)分支。AssemblyResolve事件的触发逻辑:只要CLR在预期上下文找不到依赖,就会触发事件,不管该程序集是否在其他上下文存在。你的代码返回已加载的程序集实例,相当于告诉CLR“用这个现成的”,问题得以解决,但不会执行到重新加载的分支。
args.RequestingAssembly == null判断的隐患:TPL.DataFlow.dll是被你加载的主程序集引用的,此时args.RequestingAssembly应该不为null——但你的代码直接返回args.RequestingAssembly,这其实无法解决依赖问题。之所以问题能解决,大概率是事件触发了多次,某次触发时appDomain.GetAssemblies()已能找到目标程序集。
优化建议
- 移除
args.RequestingAssembly == null的判断,否则无法处理主程序集引用的依赖解析场景。 - 不要在finally块中移除AssemblyResolve事件:
Activator.CreateInstance可能触发延迟加载的依赖,此时事件已被移除,会导致新的加载失败。 - 可以通过
AssemblyName的Location属性确认程序集加载路径,避免跨上下文解析的混乱。
内容的提问来源于stack exchange,提问作者JuanDYB

