Это очень странная проблема, которую я провел день, пытаясь отследить. Я не уверен, что это ошибка, но было бы здорово получить некоторую перспективу и мысли о том, почему это происходит.Is ConstructorInfo.GetParameters Thread-Safe?
Я использую xUnit (2.0) для запуска модульных тестов. Красота xUnit заключается в том, что она автоматически запускает тесты параллельно для вас. Однако проблема, которую я обнаружил, заключается в том, что Constructor.GetParameters
, по-видимому, не является потокобезопасным, если ConstructorInfo
помечен как поточно-безопасный тип. То есть, если два потока достигают Constructor.GetParameters
одновременно, генерируются два результата, и последующие вызовы этому методу возвращают второй результат, который был создан (независимо от потока, который его вызывает).
Я создал код для демонстрации этого неожиданного поведения (I also have it hosted on 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
(ПРИМЕЧАНИЕ: Я добавил, чтобы обозначить неожиданное значение Вы не увидите это в «< ---- Different!». результаты тестов.)
Как вы можете видеть, результат самого первого вызова GetParameters
возвращает другое значение, чем все последующие вызовы.
У меня уже был нос в .NET, но я никогда не видел ничего подобного. Это ожидаемое поведение? Есть ли предпочтительный/известный способ инициализации системы типа .NET, чтобы этого не произошло?
И наконец, если кому-то интересно, я столкнулся с этой проблемой при использовании xUnit с MEF 2, where a ParameterInfo being used as a key in a dictionary is not returning as equal to the ParameterInfo being passed in from a previously saved value. Это, конечно, приводит к неожиданному поведению и приводит к неудачным тестам при одновременном запуске.
EDIT: После некоторой хорошей обратной связи с ответами, я (надеюсь) разъяснил этот вопрос и сценарий. Ядром проблемы является «Безопасность потоков» типа «Thead-Safe» и более полное знание того, что именно это означает.
ОТВЕТ: Эта проблема закончилась тем, что быть связано с несколькими факторами, один из которых из-за меня нескончаемым невежество в многопоточных сценариях, которые он, кажется, я навсегда обучения без конца в обозримом будущем , Я снова благодарен xUnit за то, что он был разработан таким образом, чтобы таким образом изучить эту территорию.
Другая проблема, по-видимому, является несогласованностью с тем, как инициализируется система типа .NET. С TypeInfo/Type вы получаете тот же тип/reference/hashcode независимо от того, какой поток обращается к нему, сколько раз. Для MemberInfo/MethodInfo/ParameterInfo это не так. Предотвращение потоков.
И, наконец, кажется, что я не единственный человек с этой путаницей, и у этого есть indeed been recognized as an invalid assumption on a submitted issue to .NET Core's GitHub repository.
Итак, проблема решена, главным образом. Я хотел бы выслать свою благодарность и признательность всем, кто занимается моим невежеством в этом вопросе, и помог мне узнать (что я нахожу) это очень сложное проблемное пространство.
И это проблема? Что у вас есть два разных экземпляра класса с одинаковыми значениями? –
Исправить. Это один экземпляр при первом вызове, а затем другой экземпляр при каждом последующем вызове. Таким образом, один поток получит одну версию при первом вызове, а затем каждый поток получит другой (неизменный экземпляр для каждого последующего вызова. Если я использую этот первый вызов для хранения ключа (как в примере выше с MEF2), тогда да, это проблема. :) –