ConstructorInfo.GetParameters线程安全吗?

Mik*_*EEE 5 .net c# xunit.net task-parallel-library xunit2

这是一个非常奇怪的问题,我花了一天时间试图追踪.我不确定这是否是一个错误,但是对于为什么会发生这种情况获得一些观点和想法会很棒.

我正在使用xUnit(2.0)来运行我的单元测试.xUnit的优点在于它可以自动为您并行运行测试.但是,我发现的问题是,Constructor.GetParametersConstructorInfo标记为线程安全类型时,似乎不是线程安全的.也就是说,如果两个线程同时到达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存储库提交的问题的无效假设.

所以,问题大多已经解决了.我要感谢所有参与处理我在这件事上的无知,并帮助我学习(我发现的是)这个非常复杂的问题空间.

Eri*_*ert 6

它是第一次调用时的一个实例,然后是每次后续调用的另一个实例.

好没关系.有点奇怪,但是没有记录该方法,因为每次都返回相同的实例.

因此,一个线程将在第一次调用时获得一个版本,然后每个线程将获得另一个版本(在每个后续调用中都是不变的实例).

再次,奇怪,但完全合法.

这是预期的行为吗?

好吧,在你的实验之前,我没想到它.但是在你的实验之后,是的,我希望这种行为继续下去.

是否有一种首选/已知的初始化.NET类型系统的方法,以便不会发生这种情况?

据我所知.

如果我使用第一个电话来存储密钥,那么是的,这是一个问题.

然后你有证据表明你应该停止这样做.如果你这样做会伤害,不要这样做.

ParameterInfo引用应始终表示相同的ParameterInfo引用,无论其处于何种线程或访问了多少次.

这是对功能如何道德的声明应该已经设计.这不是它是如何设计的,它显然不是它是如何实现的.你当然可以认为设计是坏的.

Lippert先生也是正确的,文档不保证/指定这一点,但这一直是我对这种行为的期望和经验,直到这一点.

过去的表现并不能保证将来的结果; 直到现在,你的经历还没有变化.多线程有一种混淆人们期望的方式!一个记忆不断变化的世界,除非事情保持不变,这与我们正常的事物模式相反,直到某些东西改变它们.