Problem mit Json.Net-Deserialisierung wegen unzureichendem Arbeitsspeicher

Problem mit Json.Net-Deserialisierung wegen unzureichendem Arbeitsspeicher

Das Lesen großer JSON-Strings mit JsonConvert.DeserializeObject verbraucht viel Speicher. Eine der Möglichkeiten, dieses Problem zu überwinden, besteht darin, eine Instanz von JsonSerializer wie unten angegeben zu erstellen.

 using (StreamReader r = new StreamReader(filePath))
 {
          using (JsonReader reader = new JsonTextReader(r))
         {
                JsonSerializer serializer = new JsonSerializer();
                T lstObjects = serializer.Deserialize<T>(reader);
        }
}

Hier filePath :- ist Ihre aktuelle Json-Datei und T :- ist Ihr generisches Objekt.


Sie haben hier zwei Probleme:

  1. Sie haben ein einzelnes Base64-Datenfeld innerhalb Ihrer JSON-Antwort, die größer als ~400 MB ist.

  2. Sie laden die gesamte Antwort in eine Zwischenzeichenfolge jsonContent das ist noch größer, da es das einzelne Datenfeld einbettet.

Erstens gehe ich davon aus, dass Sie 64-Bit verwenden. Wenn nicht, wechseln.

Leider kann das erste Problem nur gemildert und nicht behoben werden, da Json.NETs JsonTextReader hat nicht die Fähigkeit, einen einzelnen Zeichenfolgenwert in "Chunks" auf die gleiche Weise wie XmlReader.ReadValueChunk() zu lesen . Es wird immer jeden atomaren String-Wert vollständig materialisieren. Aber .Net 4.5 fügt die folgenden Einstellungen hinzu, die hilfreich sein können:

  1. <gcAllowVeryLargeObjects enabled="true" /> .

  2. GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce .

    Diese Einstellung ermöglicht die Komprimierung des Heaps für große Objekte und kann Fehler wegen unzureichendem Arbeitsspeicher aufgrund von Adressraumfragmentierung reduzieren.

Das zweite Problem kann jedoch durch Streaming-Deserialisierung angegangen werden, wie in dieser Antwort auf diese Frage von Dilip0165 gezeigt wird. Effiziente API-Aufrufe mit HttpClient und JSON.NET von John Thiriet; Leistungstipps:Speichernutzung optimieren von Newtonsoft; und Streaming mit New .NET HttpClient und HttpCompletionOption.ResponseHeadersRead von Tugberk Ugurlu. Wenn Sie die Informationen aus diesen Quellen zusammentragen, sollte Ihr Code in etwa so aussehen:

Result result;
var requestJson = JsonConvert.SerializeObject(message); // Here we assume the request JSON is not too large
using (var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json"))
using (var request = new HttpRequestMessage(HttpMethod.Post, client.BaseAddress) { Content = requestContent })
using (var response = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).Result)
using (var responseStream = response.Content.ReadAsStreamAsync().Result)
{
    if (response.IsSuccessStatusCode)
    {
        using (var textReader = new StreamReader(responseStream))
        using (var jsonReader = new JsonTextReader(textReader))
        {
            result = JsonSerializer.CreateDefault().Deserialize<Result>(jsonReader);
        }
    }
    else
    {
        // TODO: handle an unsuccessful response somehow, e.g. by throwing an exception
    }
}

Oder mit async/await :

Result result;
var requestJson = JsonConvert.SerializeObject(message); // Here we assume the request JSON is not too large
using (var requestContent = new StringContent(requestJson, Encoding.UTF8, "application/json"))
using (var request = new HttpRequestMessage(HttpMethod.Post, client.BaseAddress) { Content = requestContent })
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
    if (response.IsSuccessStatusCode)
    {
        using (var textReader = new StreamReader(responseStream))
        using (var jsonReader = new JsonTextReader(textReader))
        {
            result = JsonSerializer.CreateDefault().Deserialize<Result>(jsonReader);
        }
    }
    else
    {
        // TODO: handle an unsuccessful response somehow, e.g. by throwing an exception
    }
}           

Mein obiger Code ist nicht vollständig getestet, und die Fehler- und Abbruchbehandlung muss implementiert werden. Möglicherweise müssen Sie auch das Timeout wie hier und hier gezeigt einstellen. JsonSerializer von Json.NET unterstützt keine asynchrone Deserialisierung, wodurch es etwas ungünstig zum asynchronen Programmiermodell von HttpClient passt .

Schließlich könnten Sie als Alternative zur Verwendung von Json.NET zum Lesen eines riesigen Base64-Blocks aus einer JSON-Datei den von JsonReaderWriterFactory zurückgegebenen Reader verwenden was macht unterstützt das Lesen von Base64-Daten in überschaubaren Blöcken. Weitere Informationen finden Sie in dieser Antwort auf Riesiges OData-JSON parsen, indem bestimmte Abschnitte des JSON gestreamt werden, um LOH zu vermeiden für eine Erklärung, wie eine riesige JSON-Datei mit diesem Reader gestreamt wird, und diese Antwort auf Stream von XmlReader lesen, Base64-dekodieren und Ergebnis in Datei schreiben zum Decodieren von Base64-Daten in Blöcken mit XmlReader.ReadElementContentAsBase64


Riesige base64-Strings sind an sich kein Problem, .Net unterstützt Objektgrößen von etwa 2 GB, siehe die Antwort hier. Das bedeutet natürlich nicht, dass Sie 2 GB an Informationen in einem Objekt speichern können!

Allerdings habe ich das Gefühl, dass das byte[] das Problem ist.

Wenn zu viele Elemente für ein byte[] vorhanden sind, spielt es keine Rolle, ob Sie das Ergebnis streamen oder sogar aus einer Datei auf Ihrer Festplatte lesen.

Also, nur zu Testzwecken, können Sie versuchen, den Typ von byte[] in string oder vielleicht sogar eine Liste zu ändern? Das ist nicht elegant oder vielleicht ratsam, aber es könnte den Weg zu einer besseren Lösung weisen.

Bearbeiten:

Ein weiterer Testfall zum Ausprobieren:Anstatt deserializeObject aufzurufen, versuchen Sie einfach, diese jsonContent-Zeichenfolge in einer Datei zu speichern und zu sehen, wie groß sie ist?

Warum brauchen Sie es auch im Speicher? Um welche Art von Daten handelt es sich? Mir scheint, wenn Sie diese im Speicher verarbeiten müssen, werden Sie eine schlechte Zeit haben - die Größe des Objekts ist einfach zu groß für die CLR.

Hatte aber gerade ein wenig Inspiration, wie wäre es, wenn Sie einen anderen Deserializer ausprobieren würden? Vielleicht RestSharp oder Sie können HttpClient.ReadAsAsync<T> verwenden . Es ist möglich, dass NewtonSoft selbst ein Problem hat, besonders wenn die Größe des Inhalts etwa 400 MB beträgt.