Я создаю утечку здесь?
Я использую новый JsonSerializer из пространства имен System.Text.Json в NETCore 3.0 для десериализации документов Json следующим образом:
var result = JsonSerializer.Deserialize<Response>(json, options);
Ответ определяется как:
public class Response
{
public string Foo { get; set; }
public JsonElement Bar { get; set; }
}
Тот факт, что JsonDocument реализует IDisposable, заставляет меня задуматься, сохраняя ссылку на элемент (Bar
), что может содержаться в JsonDocument, будет создавать утечки памяти?
Имейте в виду, что в целом я избегаю хранения данных как "вариантный" тип, подобный этому. К сожалению, структура Bar
Значение свойства неизвестно во время компиляции.
Мое подозрение связано с тем, что System.Text.Json рекламирует силу ленивой оценки, и я не уверен, что это связано с отложенным вводом / выводом.
1 ответ
Из краткого исследования источников (https://github.com/dotnet/corefx/blob/master/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs) кажется, что JsonDocument Dispose возвращает "арендованные" байты в общий пул массивов и выполняет некоторую общую очистку. Некоторые экземпляры JsonDocument помечены как не одноразовые, и в этом случае Dispose ничего не сделает. Вы можете проверить этот флаг для своего экземпляра с помощью отражения - если в вашем экземпляре нет внутреннего флага IsDisposable, установленного на true, не о чем беспокоиться, потому что Dispose в любом случае ничего не сделает.
Я думаю, что в обычном сценарии парсер JsonDocument должен очищать после себя, и после завершения синтаксического анализа не должно оставаться арендованных байтов или внутренних данных.
Всегда безопасно не полагаться на конкретную реализацию, поскольку она может изменять и хранить только ссылки на необходимые элементы. Вам, вероятно, следует переназначить элементы JSON в свою модель, я думаю, что вся цель десериализации JSON
Быстрый тест:
var parentField = result.Bar.GetType().GetMember("_parent", MemberTypes.Field, BindingFlags.Instance | BindingFlags.NonPublic)[0] as FieldInfo;
var parentDocument = parentField.GetValue(result.Bar);
var isDisposableProperty = parentDocument.GetType().GetProperty("IsDisposable", BindingFlags.Instance | BindingFlags.NonPublic) as PropertyInfo;
Console.WriteLine(isDisposableProperty.GetValue(parentDocument)); // false
Доказывает, что экземпляр JsonDocument, хранящийся в JsonElement, не является одноразовым.
Когда ты звонишь JsonDocument.Parse
он использует объединенные массивы, чтобы избежать пауз при сборке мусора при высокой пропускной способности.
Когда вы удаляете этот документ, он помещает массивы обратно в пул, если вы теряете ссылку и он получает сборщик мусора... это то же самое, как если бы он вообще не был одноразовым (немного хуже, поскольку некоторые другие аспекты время выполнения может внезапно оказаться там, где все приостанавливается, и GC срабатывает, потому что в пуле теперь меньше массивов).
JsonElement
имеет Clone
метод (но не ICloneable
), который возвращает копию (соответствующих) данных с использованием JsonDocument
экземпляр, который не использует объединенные массивы.
JsonSerializer при возврате JsonElement
ценности, всегда звонки Clone()
а затем утилизирует оригинал JsonDocument
. Итак, когда вы используетеJsonSerializer
и получить JsonElement
(напрямую или через свойства переполнения) это то же самое, как если бы JsonDocument
был построен без ArrayPool
оптимизация... так что все нормально.