Das ist ein sehr merkwürdiges Problem, das ich den Tag versucht habe, aufzuspüren. Ich bin mir nicht sicher, ob das ein Fehler ist, aber es wäre großartig, etwas Perspektive und Gedanken darüber zu bekommen, warum das passiert.Ist ConstructorInfo.GetParameters Thread-sicher?
Ich verwende xUnit (2.0), um meine Komponententests auszuführen. Das Schöne an xUnit ist, dass es automatisch Tests für Sie ausführt. Das Problem, das ich gefunden habe, ist jedoch, dass Constructor.GetParameters
nicht Thread-sicher scheint, wenn der ConstructorInfo
als Thread-Safe-Typ markiert ist. Das heißt, wenn zwei Threads gleichzeitig Constructor.GetParameters
erreichen, werden zwei Ergebnisse erzeugt, und nachfolgende Aufrufe dieser Methode geben das zweite Ergebnis zurück, das erstellt wurde (unabhängig vom Thread, der es aufruft).
Ich habe Code erstellt, um dieses unerwartete Verhalten zu demonstrieren (I also have it hosted on GitHub, wenn Sie das Projekt lokal herunterladen und testen möchten). Hier
ist der Code:
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) {}
}
Die beiden Testklassen sorgen dafür, dass zwei Threads erstellt und parallel ausgeführt werden. Auf diese Tests ausgeführt wird, sollten Sie die Ergebnisse erhalten, die wie folgt aussehen:
Initialized:
39053774 <---- Different!
45653674
After Initialized:
39053774 <---- Different!
45653674
45653674
45653674
(ANMERKUNG: Ich habe hinzugefügt, um den unerwarteten Wert bezeichnen Sie werden es nicht sehen in „< ---- Different!“. Die Testergebnisse.)
Wie Sie sehen können, gibt das Ergebnis vom allerersten Aufruf an GetParameters
einen anderen Wert zurück als alle nachfolgenden Aufrufe.
Ich hatte meine Nase in .NET für eine ganze Weile, aber habe noch nie so etwas gesehen. Ist das erwartetes Verhalten? Gibt es eine bevorzugte/bekannte Möglichkeit, das .NET-Typsystem zu initialisieren, damit dies nicht geschieht?
Schließlich, wenn jemand interessiert ist, stieß ich auf dieses Problem bei der Verwendung von xUnit mit 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. Dies führt natürlich zu unerwartetem Verhalten und führt bei gleichzeitiger Ausführung zu fehlgeschlagenen Tests.
EDIT: Nach einigem guten Feedback aus den Antworten habe ich (hoffentlich) diese Frage und das Szenario geklärt. Der Kern des Problems ist "Thread-Sicherheit" eines "Thead-Safe" -Typs und ein besseres Wissen darüber, was genau das bedeutet.
ANTWORT: Dieses Problem auf mehrere Faktoren zurückzuführen sein endete als, eine davon ist ich wegen nie endende Ignoranz Multi-Threaded-Szenarien, die es mir ohne Ende für die absehbare Zukunft bin für immer zu lernen scheint . Ich bin wieder dankbar dafür, dass xUnit so konzipiert wurde, dass man dieses Territorium so effektiv lernen kann.
Das andere Problem scheint Inkonsistenzen mit, wie das .NET-Typsystem initialisiert wird. Mit der TypeInfo/Type erhalten Sie den gleichen Typ/Referenz/Hashcode, egal auf welchen Thread zugegriffen wird, aber oft. Für MemberInfo/MethodInfo/ParameterInfo ist dies nicht der Fall. Thread-Zugriff Vorsicht.
Schließlich scheint es, ich bin nicht die einzige Person mit dieser Verwirrung und das hat indeed been recognized as an invalid assumption on a submitted issue to .NET Core's GitHub repository.
Also, Problem meist gelöst. Ich möchte allen, die sich mit meiner Unwissenheit in dieser Angelegenheit beschäftigen, meinen Dank und meine Anerkennung aussprechen und mir helfen, diesen sehr komplexen Problemraum zu lernen (was ich finde).
Und das ist ein Problem? Dass Sie zwei verschiedene Instanzen einer Klasse mit denselben Werten haben? –
Korrekt. Es ist eine Instanz beim ersten Aufruf und dann eine weitere Instanz bei jedem folgenden Aufruf. So erhält ein Thread beim ersten Aufruf eine Version, und dann erhält jeder Thread bei jedem weiteren Aufruf eine andere (unveränderliche Instanz). Wenn ich diesen ersten Aufruf zum Speichern eines Schlüssels verwende (wie im obigen Beispiel mit MEF2), dann Ja, das ist ein Problem :) –