【问题标题】:Assemblies mysteriously loaded into new AppDomains程序集神秘地加载到新的 AppDomains 中
【发布时间】:2010-12-14 07:20:54
【问题描述】:

我正在测试一些在程序集加载到应用程序域时可以工作的代码。对于单元测试(在 VS2k8 的内置测试主机中),我在每次测试之前启动了一个新的、唯一命名的 appdomain,并认为它应该是“干净的”:

[TestInitialize()]  
public void CalledBeforeEachTestMethod()  
{  
 AppDomainSetup appSetup = new AppDomainSetup();  
 appSetup.ApplicationBase = @"G:\<ProjectDir>\bin\Debug";
 Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;  
 Evidence evidence = new Evidence( baseEvidence );  
 _testAppDomain = AppDomain.CreateDomain( "myAppDomain" + _appDomainCounter++, evidence, appSetup );  
}  

[TestMethod]
public void MissingFactoryCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadMissingRegistrationAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

[TestMethod]
public void InvalidFactoryMethodCausesAppDomainUnload()
{
 SupportingClass supportClassObj = (SupportingClass)_testAppDomain.CreateInstanceAndUnwrap(
GetType().Assembly.GetName().Name,
typeof( SupportingClass ).FullName );  
 try  
 {  
  supportClassObj.LoadInvalidFactoriesAssembly();  
  Assert.Fail( "Should have nuked the app domain" );  
 }  
 catch( AppDomainUnloadedException ) { }  
}

public class SupportingClass : MarshalByRefObject
{
 public void LoadMissingRegistrationAssembly()  
 {  
  MissingRegistration.Main();  
 }  
 public void LoadInvalidFactoriesAssembly()  
 {  
  InvalidFactories.Main();  
 }  
}

如果每个测试都是单独运行的,我发现它可以正常工作; appdomain 被创建并且只加载了几个预期的程序集。但是,如果连续运行多个测试,则每个 _testAppDomain 都已经从所有先前的测试中加载了程序集。奇怪的是,这两个测试得到了不同名称的 appdomains。定义 MissingRegistration 和 InvalidFactories(两个不同的程序集)的测试程序集永远不会加载到单元测试的默认应用程序域中。谁能解释这种行为?

【问题讨论】:

  • 更新:它与 appSetup.ApplicationBase 有关。每次运行使用不同的路径将避免不良副作用。
  • Update2:如果在控制台应用程序中以相同的顺序运行相同的方法,则该问题不会再次出现。可能是 VS 测试主机引起了一些问题。

标签: assemblies mstest appdomain


【解决方案1】:

听起来正在发生的事情是程序集正在父 AppDomain 中加载。如果是这样,您的错误在于您如何在测试代码的其他地方使用 _testAppDomain 的细节,现在是如何创建它。

理想情况下,测试工具代码应在 AppDomain 中自行运行,然后在 AppDomain 中运行的方法应实际加载被测程序集。

下面是一个示例,说明如何防止父 AppDomain 加载您的测试程序集:

void TestRunner()
{
  testProxy =
    (TestProxy)_testAppDomain.CreateInstanceAndUnwrap(
                      typeof(TestProxy).Assembly.FullName,
                      typeof(TestProxy).FullName)

  testProxy.RunTest(testAssembly, typeName);
}

public class TestProxy : MarshalByRefObject
{
  public void Runtest(string testAssembly, string typeName)
  {
    var testType = Assembly.Load(testAssembly).GetType(typeName);

    // run tests in testType using reflection or whatever
  }
}

但是,在您的特定情况下,您可能本身并没有任何测试程序集,所以这可能不适用。

我还注意到您的 [TestInitialize] 评论说它在每个测试中调用一次,但是 IIRC,文档说 Visual Studio 的测试框架在运行多个测试时每个类只调用一次。我使用不同的框架,所以我不确定。

更新

现在我可以看到您的其余代码,我可以看到您已经采取了合理的预防措施,不在父 AppDomain 中加载程序集。你说这确实没有发生,我相信你。如果情况变得更糟,您可以尝试让 SupportingClass 调用另一个程序集,然后由它进行测试,但我真的不认为这会改变任何事情。

我确实有另一个理论给你:

我在某处(我认为是在博客上)读到 JITed 方法在 AppDomains 之间被缓存和重用,基于包含一些程序集加载规则的签名。我假设这将包括 ApplicationBase。

所以我的理论是,当 NET Framework 加载你的程序集时(或者当它加载你的 SupportingClass 时),它正在扫描它可以使用的已经 JITed 的方法。当它找到之前 JITed 的方法时,它会在该 AppDomain 中“启用”它(因为没有更好的词),这会触发它所依赖的程序集负载。

这可以解释为什么 changin ApplicationBase 使它起作用:JITed 方法不会从缓存中重用,并且由于该方法从未被调用,因此它不会再次 JITed,因此永远不会加载依赖程序集。

在我看来,您在改变 ApplicationBase 方面有一个很好的解决方法,或者如果 ApplicationBase 对于保持不变很重要,您可以尝试改变 Evidence。我认为这会产生相同的效果。

【讨论】:

  • 好主意,但没有爱。我已经更新了我的原始帖子以反映该实验的结果。我还添加了正确的格式(我以前从未发布过)。
猜你喜欢
  • 1970-01-01
  • 2013-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
  • 2017-05-15
  • 1970-01-01
相关资源
最近更新 更多