我一整天都在追踪一个非常奇怪的问题。我不确定这是否是一个错误,但很希望能够得到一些关于为什么会发生这种情况的看法和建议。
我正在使用 xUnit(2.0)来运行我的单元测试。xUnit 的美妙之处在于它自动为您并行运行测试。然而,我发现的问题是,当 ConstructorInfo
被标记为线程安全类型时,Constructor.GetParameters
似乎不是线程安全的。也就是说,如果两个线程同时到达 Constructor.GetParameters
,则会产生两个结果,并且对此方法的后续调用将返回创建的第二个结果(无论调用它的线程如何)。
我已经编写了一些代码来演示这种意外行为(如果您想要下载并在本地尝试该项目,则可以访问GitHub)。
以下是代码:
public class OneClass
{
readonly ITestOutputHelper output;
public OneClass( ITestOutputHelper output )
{
this.output = output;
}
[Fact]
public void OutputHashCode()
{
Support.Add( typeof(SampleObject).GetTypeInfo() );
output.WriteLine( "Initialized:" );
Support.Output( output );
Support.Add( typeof(SampleObject).GetTypeInfo() );
output.WriteLine( "After Initialized:" );
Support.Output( output );
}
}
public class AnotherClass
{
readonly ITestOutputHelper output;
public AnotherClass( ITestOutputHelper output )
{
this.output = output;
}
[Fact]
public void OutputHashCode()
{
Support.Add( typeof(SampleObject).GetTypeInfo() );
output.WriteLine( "Initialized:" );
Support.Output( output );
Support.Add( typeof(SampleObject).GetTypeInfo() );
output.WriteLine( "After Initialized:" );
Support.Output( output );
}
}
public static class Support
{
readonly static ICollection<int> Numbers = new List<int>();
public static void Add( TypeInfo info )
{
var code = info.DeclaredConstructors.Single().GetParameters().Single().GetHashCode();
Numbers.Add( code );
}
public static void Output( ITestOutputHelper output )
{
foreach ( var number in Numbers.ToArray() )
{
output.WriteLine( number.ToString() );
}
}
}
public class SampleObject
{
public SampleObject( object parameter ) {}
}
两个测试类确保创建并运行两个线程并行执行。运行这些测试后,您应该看到以下结果:
Initialized:
39053774 <---- Different!
45653674
After Initialized:
39053774 <---- Different!
45653674
45653674
45653674
(注意:我已经添加了“&lt; ----不同!”以表示意外值。您将在测试结果中看不到这些。)
正如您所看到的,从第一次调用GetParameters返回的结果与所有后续调用返回的结果不同。
我已经深入研究.NET相当长的时间,但从未见过像这样的情况。这是预期行为吗?有没有首选/已知的初始化.NET类型系统的方法,使其不会发生这种情况?
最后,如果有人感兴趣,我在使用MEF 2时遇到了这个问题,其中一个ParameterInfo用作字典中的键,它与以前保存的值中传递进来的ParameterInfo不相等。当然,这会导致意外的行为并在并发运行时导致测试失败。
编辑:在得到一些好的反馈后,我已经(希望)澄清了这个问题和情景。问题的核心是“线程安全”的“线程安全”类型,并获得更好的了解这是什么意思。 回答:这个问题最终是由多个因素引起的,其中之一是我对多线程场景的无尽无休的无知,似乎我永远在学习,没有预见到未来的结束。我再次感谢xUnit设计得如此出色,以便以如此有效的方式学习这个领域。
另一个问题似乎是.NET类型系统初始化的不一致性。使用TypeInfo/Type时,无论哪个线程访问它多少次,都会得到相同的类型/引用/哈希码。但对于MemberInfo/MethodInfo/ParameterInfo,则不是这种情况。要小心线程访问。
最后,似乎我不是唯一一个有这种困惑的人,这已经被认为是提交给.NET Core GitHub存储库的问题的无效假设(确实被承认)。
因此,问题解决了,大部分解决了。我想向所有参与处理我在这个问题上的无知并帮助我学习(我发现这是)这个非常复杂的问题空间的人表示感谢和赞赏。