2015-12-24 14 views
8

Rozumiem, że w .NET finalizatory są uruchamiane nawet wtedy, gdy obiekt jest częściowo skonstruowany (np. Jeśli wyjątek został wyrzucony ze swojego konstruktora), ale co się stanie, gdy konstruktor nigdy nie zostanie uruchomiony?W .NET, czy finalizator może być uruchomiony, nawet jeśli konstruktor obiektu nigdy nie działał?

Tło

Mam niektóre kodu C++/CLI, który działa skutecznie następujące (nie wierzę, to jest C++/CLI specyficzny, ale jest to sytuacja mam w pogotowiu):

try { 
    ClassA ^objA = FunctionThatReturnsAClassA(); 
    ClassB ^objB = gcnew ClassB(objA); // ClassB is written in C# in a referenced project 
    ... 
} 
catch (...) {...} 

mam 100% powtarzalny przypadek, w którym, jeśli na wyjątek z FunctionThatReturnsAClassA(), a następnie GC jest wyzwalany (wydaje się być wiarygodny wywołany ponownie uruchomić ten kod, ale czeka jakiś czas działa również) Kończy się finalizator ClassB.

Teraz za pomocą wyjścia śledzenia mogę potwierdzić, że konstruktor ClassB nie jest uruchomiony (co oczywiście jest tym, czego można się spodziewać). W ten sposób, jak się wydaje, obiekt objB został przydzielony i dodany do listy finalizatorów, zanim warunki wstępne dla wywołania jego konstruktora zostały spełnione (tj. Zbieranie wyniku z FunctionThatReturnsAClassA()).

Dzieje się tak tylko w przypadku zoptymalizowanych wersji wydań działających poza debuggerem. Istnieje wiele drobnych zmian, które mogę wprowadzić, skutkując tym, że finalizator nie działa - na przykład wstawiając inne wywołanie metody między dwiema wypowiedziami lub (co ciekawe, myślę) przesuwając "gcnew ClassB" do oddzielnej funkcji, która zwraca obiekt.

Wydaje mi się, że w jakiś sposób część alokacji instrukcji gcnew jest ponownie porządkowana i uruchamiana przed poprzednią instrukcją, ale ta zmiana kolejności NIE jest odzwierciedlana w wygenerowanym kodzie MSIL (pokonując moje początkowe założenie, że było to po prostu inne C++/Błąd genów kodu CLI). Co więcej, porównanie wygenerowanego kodu MSIL między stanem "buggy" a dowolnym stanem "stałym" nie wykazuje żadnych nieoczekiwanych zmian strukturalnych.

Przyjrzałem się również wygenerowanemu kodowi x86 w debugerze i do tej pory nie wygląda to dziwnie, ale nie analizowałem go tak głęboko, a mimo to nie mogę odtworzyć tego zachowania w debugerze, więc Nie jestem w 100% pewny, że kod, który otrzymuję z debuggera, jest taki sam jak kod, który pokazuje dziwne zachowanie.

Może to być genom kodu MSIL-> x86 lub może to być zmiana kolejności instrukcji procesora (ta pierwsza wydaje się bardziej prawdopodobna, ale nie potwierdziłam, starając się uzyskać dokładniejszy kod w pamięci, gdy wystąpi takie zachowanie - to jest mój następny krok).

Pytanie

Więc jest to ważne (z braku lepszego określenia) dla alokacji obiektu w .NET być rozwiedziony kolejność i oddzielnie od wywołania konstruktora dla tego obiektu?

+3

Jest to możliwe za przedmiot mają być przydzielone bez wywoływania któregokolwiek z jej konstruktorów poprzez wywołanie [ 'FormatterServices.GetUninitializedObject()'] (https: // msdn .microsoft.com/en-us/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx). Zarówno 'BinaryFormatter' jak i' DataContractSerializer' konstruują obiekty w ten sposób.Później, jeśli obiekt ma finalizator, zostanie sfinalizowany. Ale nie sądzę, że to jest to, co widzisz, prawda? – dbc

+0

To nie jest dokładnie to, co widzę, ale podkreśla, że ​​ramy mogą tworzyć obiekty w ten sposób i przynajmniej teoretycznie możliwe jest, że coś może zostać przerwane między alokacją a konstrukcją. Nie odpowiada jednak, czy podział tych dwóch etapów jest legalny, gdy są one uruchamiane jako część standardowego nowego/gcnew. – rationull

+0

Błędy optymalizatora JItter mogą się zdarzyć. Ale nie ten, przydział obiektu (który pobiera finalizator do uruchomienia) i wywołanie konstruktora jest pojedynczym wywołaniem funkcji pomocnika CLR. Pojedyncze połączenie nie może mieć problemów z ponownym zamówieniem. Potrzebujesz dobrej repro z udokumentowanym numerem wersji CLR i korzystasz z pomocy technicznej firmy Microsoft, aby osiągnąć sukces. –

Odpowiedz

1

Jak opisano w komentarzach, odpowiedź brzmi "Tak" - finalizator może zostać uruchomiony, jeśli konstruktor nie uruchomił się lub nie ukończył. Jednak finalizator nie może działać, jeśli alokacja nie nastąpiła (co jest niezależne od wywołania konstruktora).

To już potwierdzone być bug optymalizacji JIT: https://github.com/dotnet/coreclr/issues/2478