Давайте рассмотрим этот очень простой асинхронный метод:
static async Task myMethodAsync()
{
await Task.Delay(500);
}
Когда я компилирую это с помощью VS2013 (до компилятора Roslyn), сгенерированный конечный автомат представляет собой структуру.
private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Когда я компилирую его с VS2015 (Roslyn), сгенерированный код выглядит так:
private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
Как видите, Roslyn генерирует класс (а не структуру). Если я правильно помню, первые реализации поддержки async / await в старом компиляторе (я думаю, CTP2012) также генерировали классы, а затем он был изменен на структуру из соображений производительности. (в некоторых случаях можно полностью избежать боксов и выделения кучи…) (См. это )
Кто-нибудь знает, почему это снова поменяли в Roslyn? (У меня нет проблем с этим, я знаю, что это изменение прозрачное и не меняет поведения какого-либо кода, мне просто любопытно)
Редактировать:
Ответ от @Damien_The_Unbeliever (и исходный код :)) imho все объясняет. Описанное поведение Roslyn применяется только для отладочной сборки (и это необходимо из-за ограничения CLR, упомянутого в комментарии). В Release он также генерирует структуру (со всеми преимуществами этого ..). Таким образом, это кажется очень умным решением для поддержки как редактирования, так и продолжения, а также повышения производительности в производственной среде. Интересный материал, спасибо всем, кто участвовал!
источник
async
методы почти всегда имеют истинную асинхронную точку - точку,await
которая дает управление, которое в любом случае потребует упаковки структуры. Я считаю, что структуры только уменьшат давление на память дляasync
методов, которые выполняются синхронно.Ответы:
Я не знал об этом заранее, но, поскольку в наши дни Roslyn имеет открытый исходный код, мы можем поискать объяснения по коду.
И здесь, в строке 60 AsyncRewriter , мы находим:
// The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class. var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;
Таким образом, несмотря на то, что есть некоторая привлекательность в использовании
struct
s, большая победа в том, что разрешить Edit и Continue работать внутриasync
методов, была явно выбрана как лучший вариант.источник
Трудно дать окончательный ответ на что-то вроде этого (если только кто-то из команды компиляторов не заглянет :)), но есть несколько моментов, которые вы можете учесть:
«Бонус» производительности структур всегда является компромиссом. В основном вы получаете следующее:
Что это означает в случае ожидания? Ну вообще ... ничего. Существует только очень короткий период времени, в течение которого конечный автомат находится в стеке - помните,
await
эффективно выполняет areturn
, поэтому стек методов умирает; конечный автомат должен где-то сохраняться, а это «где-то» точно лежит в куче. Время жизни стека плохо подходит для асинхронного кода :)Помимо этого, конечный автомат нарушает некоторые хорошие рекомендации по определению структур:
struct
s должен быть не более 16 байтов - конечный автомат содержит два указателя, которые сами по себе заполняют 16-байтовый предел на 64-битном. Кроме того, есть само состояние, поэтому оно выходит за «лимит». Это не имеет большого значения , поскольку, скорее всего, он всегда передается только по ссылке, но обратите внимание, что это не совсем подходит для варианта использования структур - структуры, которая в основном является ссылочным типом.struct
s должен быть неизменным - ну, это, вероятно, не требует особых комментариев. Это государственная машина . Опять же, это не имеет большого значения, поскольку структура является автоматически сгенерированным кодом и закрытой, но ...struct
s должен логически представлять одно значение. Определенно не здесь, но это уже как бы следует из наличия изменяемого состояния в первую очередь.И, конечно же, все это в том случае, когда нет закрытий. Когда у вас есть локальные переменные (или поля), которые пересекают
await
s, состояние еще больше увеличивается, что ограничивает полезность использования структуры.Учитывая все это, классовый подход определенно чище, и я бы не ожидал заметного увеличения производительности от использования
struct
вместо него. Все объекты , задействованные имеют одинаковую продолжительность жизни, так что единственный способ улучшить производительность памяти было бы сделать все из нихstruct
с (магазин в некотором буфере, например) - что невозможно в общем случае, конечно. И в большинстве случаев, когда вы использовали быawait
в первую очередь (то есть некоторая работа с асинхронным вводом-выводом), уже вовлекают другие классы - например, буферы данных, строки ... Маловероятно, что вы будете что-await
то, что просто возвращается42
без каких-либо действий. распределения кучи.В конце концов, я бы сказал, что единственное место, где вы действительно увидите реальную разницу в производительности, - это тесты. А оптимизация для тестов - это, мягко говоря, глупая идея ...
источник