Mik*_*EEE 5 .net c# xunit.net task-parallel-library xunit2
这是一个非常奇怪的问题,我花了一天时间试图追踪.我不确定这是否是一个错误,但是对于为什么会发生这种情况获得一些观点和想法会很棒.
我正在使用xUnit(2.0)来运行我的单元测试.xUnit的优点在于它可以自动为您并行运行测试.但是,我发现的问题是,Constructor.GetParameters当ConstructorInfo标记为线程安全类型时,似乎不是线程安全的.也就是说,如果两个线程同时到达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 ) {}
}
Run Code Online (Sandbox Code Playgroud)
这两个测试类确保创建并运行两个线程.运行这些测试后,您应该得到如下所示的结果:
Initialized:
39053774 <---- Different!
45653674
After Initialized:
39053774 <---- Different!
45653674
45653674
45653674
Run Code Online (Sandbox Code Playgroud)
(注意:我添加了"<---- Different!"来表示意外的值.你不会在测试结果中看到这一点.)
如您所见,第一次调用的结果GetParameters返回的值与所有后续调用的值不同.
我已经在.NET中使用了很长一段时间,但从未见过这样的东西.这是预期的行为吗?是否有一种首选/已知的初始化.NET类型系统的方法,以便不会发生这种情况?
最后,如果有人感兴趣,我在使用带有MEF 2的xUnit时会遇到这个问题,其中用作字典中的键的ParameterInfo不会返回等于从先前保存的值传入的ParameterInfo.当然,这会导致意外行为并导致并发运行时测试失败.
编辑:在答案得到一些好的反馈之后,我(希望)澄清了这个问题和场景.问题的核心是"Thead-Safe"类型的"线程安全",并且更好地了解这意味着什么.
答案:这个问题最终归因于几个因素,其中之一是由于我对多线程场景永无止境的无知,这似乎是我在可预见的未来永远学习无止境.我再次感谢xUnit的设计,以这种有效的方式学习这片领土.
另一个问题似乎与.NET类型系统初始化的方式不一致.使用TypeInfo/Type,无论哪个线程多次访问它,您都会获得相同的类型/引用/哈希码.对于MemberInfo/MethodInfo/ParameterInfo,情况并非如此.线程访问要小心.
最后,似乎我不是唯一有这种困惑的人,这确实被认为是对.NET Core的GitHub存储库提交的问题的无效假设.
所以,问题大多已经解决了.我要感谢所有参与处理我在这件事上的无知,并帮助我学习(我发现的是)这个非常复杂的问题空间.
它是第一次调用时的一个实例,然后是每次后续调用的另一个实例.
好没关系.有点奇怪,但是没有记录该方法,因为每次都返回相同的实例.
因此,一个线程将在第一次调用时获得一个版本,然后每个线程将获得另一个版本(在每个后续调用中都是不变的实例).
再次,奇怪,但完全合法.
这是预期的行为吗?
好吧,在你的实验之前,我没想到它.但是在你的实验之后,是的,我希望这种行为继续下去.
是否有一种首选/已知的初始化.NET类型系统的方法,以便不会发生这种情况?
据我所知.
如果我使用第一个电话来存储密钥,那么是的,这是一个问题.
然后你有证据表明你应该停止这样做.如果你这样做会伤害,不要这样做.
ParameterInfo引用应始终表示相同的ParameterInfo引用,无论其处于何种线程或访问了多少次.
这是对功能如何道德的声明应该已经设计.这不是它是如何被设计的,它显然不是它是如何实现的.你当然可以认为设计是坏的.
Lippert先生也是正确的,文档不保证/指定这一点,但这一直是我对这种行为的期望和经验,直到这一点.
过去的表现并不能保证将来的结果; 直到现在,你的经历还没有变化.多线程有一种混淆人们期望的方式!一个记忆不断变化的世界,除非事情保持不变,这与我们正常的事物模式相反,直到某些东西改变它们.
| 归档时间: |
|
| 查看次数: |
394 次 |
| 最近记录: |