Wprowadzenie do ostrzeżeń AOT

Podczas publikowania aplikacji jako natywnej AOT proces kompilacji generuje cały kod natywny i struktury danych wymagane do obsługi aplikacji w czasie wykonywania. Różni się to od wdrożeń innych niż natywne, które wykonują aplikację z formatów opisujących aplikację w sposób abstrakcyjny (program dla maszyny wirtualnej) i tworzą natywne reprezentacje na żądanie w czasie wykonywania.

Abstrakcyjne reprezentacje części programu nie mają mapowania jeden do jednego na reprezentację natywną. Na przykład abstrakcyjny opis metody ogólnej List<T>.Add mapuje na potencjalnie nieskończone ciała metody natywnej, które muszą być wyspecjalizowane dla danej T metody (na przykład List<int>.Add i List<double>.Add).

Ponieważ relacja kodu abstrakcyjnego z kodem natywnym nie jest jednorazowa, proces kompilacji musi utworzyć pełną listę natywnych treści kodu i struktur danych w czasie kompilacji. Utworzenie tej listy w czasie kompilacji dla niektórych interfejsów API platformy .NET może być trudne. Jeśli interfejs API jest używany w sposób, który nie był oczekiwany w czasie kompilacji, wyjątek zostanie zgłoszony w czasie wykonywania.

Aby zapobiec zmianom zachowania podczas wdrażania jako natywna funkcja AOT, zestaw SDK platformy .NET zapewnia statyczną analizę zgodności funkcji AOT za pomocą "ostrzeżeń AOT". Ostrzeżenia dotyczące AOT są generowane, gdy kompilacja znajdzie kod, który może nie być zgodny z usługą AOT. Kod, który nie jest zgodny z funkcją AOT, może generować zmiany behawioralne, a nawet ulegać awarii w aplikacji po utworzeniu jej jako natywnej AOT. W idealnym przypadku wszystkie aplikacje korzystające z natywnej funkcji AOT nie powinny mieć ostrzeżeń dotyczących AOT. Jeśli istnieją jakiekolwiek ostrzeżenia dotyczące funkcji AOT, upewnij się, że nie ma żadnych zmian zachowania, dokładnie testując aplikację po utworzeniu aplikacji jako natywnej AOT.

Przykłady ostrzeżeń dotyczących funkcji AOT

W przypadku większości kodu w języku C# łatwo jest określić, jaki kod natywny musi zostać wygenerowany. Kompilator macierzysty może poznać elementy metody i znaleźć, do czego uzyskuje się dostęp kod natywny i struktury danych. Niestety, niektóre funkcje, takie jak odbicie, stanowią znaczący problem. Spójrzmy na poniższy kod:

Type t = typeof(int);
while (true)
{
    t = typeof(GenericType<>).MakeGenericType(t);
    Console.WriteLine(Activator.CreateInstance(t));
}

struct GenericType<T> { }

Chociaż powyższy program nie jest bardzo przydatny, reprezentuje skrajny przypadek, który wymaga nieskończonej liczby typów ogólnych do utworzenia podczas kompilowania aplikacji jako natywnej AOT. Bez natywnej AOT program będzie uruchamiany, dopóki nie zabraknie pamięci. W przypadku natywnej funkcji AOT nie będziemy w stanie utworzyć jej nawet wtedy, gdy będziemy generować wszystkie niezbędne typy (nieskończona liczba z nich).

W takim przypadku kompilacja natywna AOT powoduje problemy z następującym ostrzeżeniem w MakeGenericType wierszu:

AOT analysis warning IL3050: Program.<Main>$(String[]): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.

W czasie wykonywania aplikacja rzeczywiście zgłosi wyjątek od wywołania MakeGenericType .

Reagowanie na ostrzeżenia dotyczące AOT

Ostrzeżenia AOT mają na celu wprowadzenie przewidywalności do natywnych kompilacji AOT. Większość ostrzeżeń dotyczących funkcji AOT dotyczy możliwego wyjątku w czasie wykonywania w sytuacjach, gdy kod natywny nie został wygenerowany w celu obsługi scenariusza. Najszersza kategoria to RequiresDynamicCodeAttribute.

RequiresDynamicCode

RequiresDynamicCodeAttribute jest prosty i szeroki: jest to atrybut, który oznacza, że element członkowski został oznaczony jako niezgodny z AOT. Ta adnotacja oznacza, że element członkowski może używać odbicia lub innego mechanizmu do tworzenia nowego kodu natywnego w czasie wykonywania. Ten atrybut jest używany, gdy kod nie jest zasadniczo zgodny z funkcją AOT lub zależność natywna jest zbyt złożona, aby statycznie przewidywać w czasie kompilacji. Często dotyczy to metod korzystających z interfejsu Type.MakeGenericType API, emisji odbicia lub innych technologii generowania kodu w czasie wykonywania. Poniższy kod przedstawia przykład.

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

void TestMethod()
{
    // IL3050: Using method 'MethodWithReflectionEmit' which has 'RequiresDynamicCodeAttribute'
    // can break functionality when AOT compiling. Use 'MethodFriendlyToAot' instead.
    MethodWithReflectionEmit();
}

Nie ma wielu obejść dla programu RequiresDynamicCode. Najlepszym rozwiązaniem jest unikanie wywoływania metody w ogóle podczas kompilowania jako natywnej funkcji AOT i używania innego elementu zgodnego z funkcją AOT. Jeśli piszesz bibliotekę i nie znajduje się ona w kontrolce, czy wywołać metodę, możesz również dodać RequiresDynamicCode ją do własnej metody. Spowoduje to dodawanie adnotacji do metody jako niezgodnej z usługą AOT. Dodanie RequiresDynamicCode powoduje wyciszenie wszystkich ostrzeżeń AOT w metodzie z adnotacjami, ale spowoduje wygenerowanie ostrzeżenia za każdym razem, gdy ktoś inny go wywoła. Z tego powodu jest to w większości przydatne dla autorów bibliotek, aby "bąbelkować" ostrzeżenie do publicznego interfejsu API.

Jeśli w jakiś sposób można określić, że wywołanie jest bezpieczne, a cały kod natywny będzie dostępny w czasie wykonywania, możesz również pominąć ostrzeżenie przy użyciu polecenia UnconditionalSuppressMessageAttribute. Na przykład:

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

[UnconditionalSuppressMessage("Aot", "IL3050:RequiresDynamicCode",
    Justification = "The unfriendly method is not reachable with AOT")]
void TestMethod()
{
    If (RuntimeFeature.IsDynamicCodeSupported)
        MethodWithReflectionEmit(); // warning suppressed
}

UnconditionalSuppressMessage jest jak SuppressMessage , ale można go zobaczyć za pomocą publish innych narzędzi po kompilacji. SuppressMessage dyrektywy i #pragma są obecne tylko w źródle, więc nie można ich używać do wyciszenia ostrzeżeń z kompilacji.

Uwaga

Zachowaj ostrożność podczas pomijania ostrzeżeń dotyczących AOT. Wywołanie może być teraz zgodne z funkcją AOT, ale podczas aktualizowania kodu może to ulec zmianie i możesz zapomnieć o przejrzeniu wszystkich pomijań.