Поделиться через


Общие сведения о предупреждениях AOT

При публикации приложения как Native AOT процесс сборки создает весь машинный код и структуры данных, необходимые для поддержки приложения в процессе выполнения. Это отличается от ненативных развертываний, которые выполняют приложение из форматов, описывающих его в абстрактных терминах (программа для виртуальной машины) и создают собственные представления по запросу во время выполнения.

Абстрактное представление частей программы не имеет однозначного соответствия с нативным представлением. Например, абстрактное описание универсального List<T>.Add метода сопоставляется с потенциально бесконечными собственными телами методов, которые должны быть специализированы для данного T (например, List<int>.Add и List<double>.Add).

Поскольку связь абстрактного кода с машинным кодом не является однозначной, процесс сборки должен создать полный список фрагментов машинного кода и структур данных во время сборки. Составление этого списка во время сборки может быть затруднительным для некоторых API .NET. Если API используется таким образом, который не ожидался во время сборки, исключение будет создано во время выполнения.

Чтобы предотвратить изменения в поведении при развертывании как Native AOT, пакет SDK для .NET предоставляет статический анализ совместимости, используя предупреждения AOT. Предупреждения AOT генерируются, когда сборка обнаруживает код, который может быть несовместим с AOT. Код, несовместимый с AOT, может привести к изменению поведения или даже сбою в приложении после его создания в формате Native AOT. В идеале все приложения, использующие собственный AOT, не должны иметь предупреждений AOT. Если есть предупреждения AOT, убедитесь, что нет изменений в поведении приложения, внимательно протестировав его после того, как оно будет собрано как Native AOT.

Примеры предупреждений AOT

Для большинства кода C# просто определить, какой машинный код необходимо создать. Собственный компилятор может анализировать тела методов и обнаруживать, к каким структурам нативного кода и данных осуществляется доступ. К сожалению, некоторые функции, такие как отражение, представляют значительную проблему. Рассмотрим следующий код:

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

struct GenericType<T> { }

Хотя указанная выше программа не очень полезна, она представляет собой крайний случай, требующий неограниченного числа универсальных типов при создании приложения как Native AOT. Без собственного AOT программа будет работать до тех пор, пока не закончится память. С помощью собственного AOT мы не смогли бы его даже собрать, если бы мы пытались сгенерировать все необходимые типы (их бесконечное количество).

В этом случае сборка Native AOT выдает следующее предупреждение в строке MakeGenericType :

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.

Во время выполнения приложение действительно вызовет исключение из вызова MakeGenericType.

Реагирование на предупреждения AOT

Предупреждения AOT предназначены для обеспечения прогнозируемости сборок Native AOT. Большинство предупреждений AOT касаются возможных исключений во время выполнения в ситуациях, когда нативный код не был создан для поддержки этого сценария. Самая широкая категория — это RequiresDynamicCodeAttribute.

Требуется DynamicCode

RequiresDynamicCodeAttribute простой и широкий: это атрибут, который означает, что член был аннотирован как несовместимый с AOT. Эта заметка означает, что член может использовать отражение или другой механизм для создания нового машинного кода во время выполнения. Этот атрибут используется, если код принципиально не совместим с AOT, или собственная зависимость слишком сложна для статического прогнозирования во время сборки. Это зачастую верно для методов, использующих Type.MakeGenericType API, эмиссию отражений и другие технологии создания кода во время выполнения. В следующем коде показан пример.

[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();
}

Существует не так много обходных RequiresDynamicCodeрешений. Лучшее решение - избегать вызова метода при разработке в среде Native AOT и использовать что-то совместимое с AOT. Если вы пишете библиотеку, и у вас нет контроля над тем, вызывается ли метод, вы также можете добавить RequiresDynamicCode в собственный метод. Это аннотирует ваш метод как несовместимый с AOT. Добавление RequiresDynamicCode подавляет все предупреждения AOT в аннотированном методе, но вызывает предупреждение всякий раз, когда его вызывает кто-то другой. По этой причине авторам библиотек в основном полезно перенести предупреждение к общедоступным API.

Если вы можете каким-то образом определить, что вызов является безопасным, и весь машинный код будет доступен во время выполнения, вы также можете отключить предупреждение с помощью UnconditionalSuppressMessageAttribute. Рассмотрим пример.

[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 похоже на SuppressMessage, но может быть увидено publish и другими пост-сборочными инструментами. SuppressMessage и #pragma директивы присутствуют только в источнике, поэтому их нельзя использовать для подавления предупреждений сборки.

Осторожность

Будьте осторожны при подавлении предупреждений AOT. Вызов может быть совместим с AOT сейчас, но при обновлении кода это может измениться, и вы можете забыть пересмотреть все подавления.