5 Gründe, warum Sie die Verwendung von System.Drawing in ASP.NET einstellen sollten

Hallo habr Ich präsentiere Ihnen die Übersetzung des Artikels „5 Gründe, warum Sie die Verwendung von System.Drawing aus ASP.NET beenden sollten“ .

Bild

Nun, sie haben es geschafft. Das corefx-Team stimmte schließlich zahlreichen Anfragen zu und nahm System.Drawing in .NET Core auf. (Originalartikel vom Juli 2017)

Das kommende System.Drawing.Common- Paket enthält den größten Teil der System.Drawing-Funktionalität von .NET Framework und ist als Kompatibilitätsoption für Benutzer gedacht, die auf .NET Core migrieren möchten, dies jedoch aufgrund von Abhängigkeiten nicht können. Aus dieser Sicht macht Microsoft das Richtige. Die Reibung muss verringert werden, da die Einführung von .Net Core ein lohnenderes Ziel ist.

Andererseits ist System.Drawing einer der ärmsten und am stärksten benachteiligten Bereiche des .Net Frameworks, und viele von uns hofften, dass die Implementierung von .NET Core den langsamen Tod von System.Drawing bedeuten würde. Und zusammen mit diesem Tod sollte sich eine Gelegenheit bieten, etwas Besseres zu tun.

Das Mono-Team hat beispielsweise einen .NET-kompatiblen Wrapper für Googles plattformübergreifende Skia- Grafikbibliothek namens SkiaSharp erstellt . Um die Installation zu vereinfachen, hat Nuget bei der Unterstützung nativer Bibliotheken für jede Plattform einen langen Weg zurückgelegt. Skia ist ziemlich voll ausgestattet und seine Leistung macht System.Drawing.

Das ImageSharp- Team hat auch großartige Arbeit geleistet und einen Großteil der System.Drawing-Funktionalität wiederholt, jedoch mit der besten API und 100% C # -Implementierung. Sie sind immer noch nicht für eine produktive Nutzung bereit, aber es scheint, dass sie bereits nahe genug daran sind. Eine kleine Warnung zu dieser Bibliothek, da es sich um die Verwendung in Serveranwendungen handelt: In der Standardkonfiguration wird jetzt Parallel.For verwendet, um einige Vorgänge zu beschleunigen. Dies bedeutet, dass möglicherweise mehr Workflows aus dem ASP.NET-Pool verwendet werden Dies führt zu einer Reduzierung des Gesamtdurchsatzes der Anwendung . Ich hoffe, dass dieses Verhalten vor der Veröffentlichung überprüft wird, aber selbst jetzt reicht es aus, eine Zeile der Konfiguration zu ändern, um sie für die Verwendung auf dem Server besser geeignet zu machen.

In jedem Fall sollten Sie beim Zeichnen, Plotten oder Rendern von Text in Bildern in einer Anwendung auf dem Server ernsthaft in Betracht ziehen, System.Drawing auf irgendetwas zu ändern, unabhängig davon, ob Sie zu .NET Core wechseln oder nicht.

Ich für meinen Teil habe eine leistungsstarke Bildverarbeitungs-Pipeline für .NET und .NET Core zusammengestellt, die eine Bildqualität bietet, die System.Drawing nicht bieten kann, und zwar in einer hochskalierbaren Architektur, die speziell für die Verwendung auf dem Server entwickelt wurde. Bisher ist es nur für Windows, jedoch plattformübergreifend geplant. Wenn Sie System.Drawing (oder etwas anderes) verwenden, um die Größe von Bildern auf dem Server zu ändern, sollten Sie MagicScaler als Ersatz in Betracht ziehen .

Aber eine System.Drawing-Wiederbelebung, die einigen Entwicklern den Übergang erleichtert, wird wahrscheinlich den größten Teil der Dynamik dieser Projekte zunichte machen, da Entwickler gezwungen waren, nach Alternativen zu suchen. Leider werden im .NET-Ökosystem Microsoft-Bibliotheken und -Pakete immer gewinnen, egal wie überlegen die Alternativen sein können.

Dieser Beitrag ist ein Versuch, einige System.Drawing-Fehler zu beheben, in der Hoffnung, dass Entwickler Alternativen untersuchen, auch wenn System.Drawing eine Option bleibt.

Ich beginne mit dem häufig zitierten Haftungsausschluss aus der System.Drawing-Dokumentation. Diese Ablehnung wurde einige Male in einer Diskussion über Github im Zusammenhang mit System.Drawing.Common angesprochen .
„Klassen mit dem System.Drawing-Namespace werden für die Verwendung in Windows- oder ASP.NET-Diensten nicht unterstützt. Der Versuch, diese Klassen mit diesen Arten von Anwendungen zu verwenden, kann zu unerwarteten Problemen führen, wie z. B. einer verringerten Serverleistung und Laufzeitfehlern. "

Wie viele von Ihnen habe ich diesen Haftungsausschluss vor sehr langer Zeit gelesen, ihn dann übersprungen und in meiner ASP.NET-Anwendung immer noch System.Drawing verwendet. Warum? Weil ich Gefahr liebe. Entweder das, oder es wurden keine anderen realisierbaren Optionen gefunden. Und weißt du was? Es ist nichts Schlimmes passiert. Höchstwahrscheinlich hätte ich das nicht sagen sollen, aber ich wette, dass viele von Ihnen das Gleiche erlebt haben. Warum also nicht weiterhin System.Drawing oder darauf basierende Bibliotheken verwenden?

Grund 1: GDI-Deskriptoren


Wenn bei der Verwendung von System.Drawing auf einem Server jemals Probleme aufgetreten sind, ist dies höchstwahrscheinlich der Fall. Wenn dies nicht getestet wird, ist dies eine der wahrscheinlichsten Ursachen.

System.Drawing ist größtenteils ein dünner Wrapper um die Windows GDI + -API. Viele System.Drawing-Objekte werden von GDI-Deskriptoren unterstützt und haben ein quantitatives Limit für den Prozessor und die Benutzersitzung. Wenn dieser Schwellenwert erreicht ist, wird die Ausnahme "Nicht genügend Speicher" und / oder der allgemeine GDI + -Fehler angezeigt.

Das Problem ist, dass in .NET die Garbage Collection und die Prozessbeendigung die Freigabe dieser Deskriptoren um den Zeitpunkt verzögern können, an dem Sie das Limit erreichen, selbst unter geringer Last. Wenn Sie vergessen haben (oder nicht wissen, was Sie brauchen), um Dispose () für ein Objekt aufzurufen, das solche Deskriptoren enthält, besteht ein hohes Risiko, dass in Ihrer Umgebung solche Fehler auftreten. Und wie die meisten Fehler im Zusammenhang mit Ressourcenbeschränkungen oder -lecks wird diese Situation höchstwahrscheinlich erfolgreich getestet und Sie in den produktiven Betrieb versetzt. Dies geschieht natürlich, wenn Ihre Anwendung am stärksten ausgelastet ist, so dass die maximale Anzahl von Benutzern von Ihrer Schande erfährt.

Die Einschränkungen für den Prozessor und die Benutzersitzung hängen von der Version des Betriebssystems ab, und die Einschränkungen für den Prozessor können angepasst werden. Aber die Version spielt keine Rolle, weil GDI-Deskriptoren werden intern durch den USHORT-Datentyp dargestellt, daher gibt es ein striktes Limit von 65.536 Deskriptoren pro Benutzersitzung, und selbst eine gut geschriebene Anwendung kann dieses Limit unter ausreichender Last erreichen. Wenn Sie glauben, dass ein leistungsfähigerer Server es Ihnen ermöglicht, immer mehr Benutzer gleichzeitig auf einer Instanz zu bedienen, wird dieses Risiko realer. Und wer möchte eigentlich Software mit einer bekannten harten Skalierbarkeitsgrenze erstellen?

Grund Nr. 2: Parallelität


GDI + hatte immer Probleme mit der Parallelität, obwohl viele von ihnen mit Architekturänderungen in Windows 7 / Windows Server 2008 R2 zusammenhängen , sehen Sie immer noch einige von ihnen in neuen Versionen. Am bemerkenswertesten ist die von GDI + während der DrawImage () - Operation angeordnete Prozesssperre. Wenn Sie die Größe von Bildern auf dem Server mithilfe von System.Drawing (oder den Bibliotheken, in denen sie enthalten sind) ändern, ist die DrawImage () -Methode wahrscheinlich die Grundlage für diesen Code.

Wenn Sie DrawImage () mehrere Male gleichzeitig aufrufen, werden alle blockiert, bis alle ausgeführt wurden. Auch wenn die Antwortzeit für Sie kein Problem darstellt (warum nicht? Hassen Sie Ihre Benutzer?) Beachten Sie, dass alle mit diesen Anforderungen verknüpften Speicherressourcen und alle GDI-Deskriptoren, die von den mit diesen Anforderungen verknüpften Objekten gespeichert werden, an die Laufzeit gebunden sind. Tatsächlich ist der Server nicht zu stark belastet, um Probleme zu verursachen.

Natürlich gibt es für dieses spezielle Problem Problemumgehungen. Einige Entwickler erstellen beispielsweise für jede DrawImage () - Operation einen externen Prozess. Tatsächlich sorgt eine solche Problemumgehung jedoch nur für zusätzliche Zerbrechlichkeit, die Sie eigentlich nicht hätten tun dürfen.

Grund 3: Gedächtnis


Stellen Sie sich einen ASP.NET-Handler vor, der ein Diagramm generiert. Er sollte so etwas tun:

  1. Erstellen Sie eine Bitmap als Zeichenfläche
  2. Zeichnen Sie mit Stiften und / oder Pinseln mehrere Formen auf eine Bitmap
  3. Zeichnen Sie Text mit einer oder mehreren Schriftarten
  4. Speichern Sie die Bitmap als PNG in MemoryStream

Angenommen, das Diagramm misst 600 mal 400 Punkte. Dies sind insgesamt 240.000 Punkte, multipliziert mit 4 Bytes für einen Punkt für das Standard-RGBA-Format, insgesamt 960.000 Bytes für eine Bitmap sowie ein wenig für das Zeichnen von Objekten und einen Speicherpuffer. Lassen Sie es 1 MB für die gesamte Anforderung sein. Wahrscheinlich treten in einem solchen Szenario keine Speicherprobleme auf, und wenn Sie auf etwas stoßen, ist die Anzahl der Deskriptoren, die ich bereits erwähnt habe, höchstwahrscheinlich begrenzt, da Bilder, Pinsel, Stifte und Schriftarten ihre eigenen Deskriptoren haben.

Das eigentliche Problem tritt auf, wenn System.Drawing für Imaging-Aufgaben verwendet wird. System.Drawing ist in erster Linie eine Grafikbibliothek, und Grafikbibliotheken basieren in der Regel auf der Idee, dass sich alles als Bitmap im Speicher befindet. Dies ist großartig, während Sie über die kleinen Dinge nachdenken. Aber die Bilder können wirklich groß sein und werden jeden Tag größer, weil Kameras mit vielen Megapixeln werden immer billiger.

Wenn Sie den naiven Ansatz von System.Drawing für die Image-Erstellung wählen, erhalten Sie für den Resize-Handler Folgendes:

  1. Erstellen Sie eine Bitmap als Zeichenfläche für das Zielbild.
  2. Laden Sie das Originalbild in eine andere Bitmap.
  3. Rufen Sie DrawImage () mit dem Parameter "image-source" für das Zielbild auf und ändern Sie die Größe.
  4. Speichern Sie die Ziel-Bitmap im JPEG-Format im Memory Stream.

Angenommen, das Ziel-Image ist 600 x 400, wie im vorherigen Beispiel, dann haben wir wieder 1 MB für das Ziel-Image und den Speicher-Stream. Angenommen, jemand hat ein 24-Megapixel-Bild von seinen neuen DSLRs hochgeladen. Dann benötigen wir 6000 x 4000 Pixel mit jeweils 3 Byte (72 MB) für die decodierte Quell-Bitmap im RGB-Format. Und wir werden das HighQualityBicubic-Resampling von System.Drawing verwenden, da es nur gut aussieht. Dann müssen wir die anderen 6000x4000 Punkte mit jeweils 4 Bytes für die PRGBA-Konvertierung berücksichtigen, die innerhalb der aufgerufenen Methode durchgeführt wird , wobei zusätzliche 96 MB belegter Speicher hinzugefügt werden. Insgesamt werden 169 MB (!) Für eine Anforderung zum Konvertieren eines einzelnen Bildes erhalten.

Stellen Sie sich nun vor, Sie haben mehr als einen Benutzer, der solche Dinge ausführt. Denken Sie jetzt daran, dass Anforderungen blockiert werden, bis alle vollständig ausgeführt wurden. Wie lange dauert es, bis der Speicher voll ist? Und selbst wenn Sie nicht befürchten, dass Sie alle verfügbaren Ressourcen vollständig ausschöpfen, gibt es viele Möglichkeiten, den Speicher Ihres Servers besser zu nutzen, als eine Reihe von Pixeln zu speichern. Berücksichtigen Sie die Auswirkung des Speicherdrucks auf andere Teile der Anwendung / des Systems:

  1. Der ASP.NET-Cache beginnt möglicherweise, Elemente zu leeren, deren Neuerstellung kostspielig ist
  2. Der Garbage Collector wird häufiger gestartet, wodurch die Anwendung verlangsamt wird
  3. IIS-Kernel-Cache oder Windows-Dateisystem-Cache können nützliche Elemente entfernen
  4. Der Anwendungspool überschreitet möglicherweise das Speicherlimit und wird möglicherweise neu gestartet
  5. Windows beginnt möglicherweise mit dem Auslagern von Speicher auf die Festplatte, wodurch das gesamte System verlangsamt wird

Willst du wirklich nichts davon?

Speziell für Bildverarbeitungsaufgaben konzipierte Bibliotheken gehen dieses Problem auf ganz andere Weise an. Sie müssen nicht das gesamte Quell- oder Zielbild in den Speicher laden. Wenn Sie nicht darauf zeichnen möchten, benötigen Sie keine Zeichenfläche / Bitmap. Dies geschieht eher so:

  1. Erstellen Sie einen Stream für den JPEG-Encoder des Zielbilds
  2. Laden Sie eine Zeile aus dem Originalbild und komprimieren Sie sie horizontal
  3. Wiederholen Sie diesen Vorgang so oft wie nötig, um eine Zeile für die Zieldatei zu bilden
  4. Komprimieren Sie die resultierenden Linien vertikal
  5. Wiederholen Sie ab Schritt 2, bis alle Zeilen der Quelldatei verarbeitet sind.

Mit dieser Methode kann dasselbe Bild mit insgesamt 1 MB Speicher verarbeitet werden, und selbst bei viel größeren Bildern ist ein geringfügig höherer Overhead erforderlich.

Ich kenne nur eine .NET-Bibliothek, die nach diesem Prinzip optimiert ist, und ich gebe Ihnen einen Hinweis: Dies ist nicht System.Drawing.

Grund 4: CPU


Ein weiterer Nebeneffekt von System.Drawing, das grafisch orientierter als bildorientierter ist, besteht darin, dass DrawImage () in Bezug auf die CPU-Auslastung ziemlich ineffizient ist. Ich habe dies in einem früheren Beitrag ausführlich behandelt, aber diese Diskussion kann durch die folgenden Fakten zusammengefasst werden:

  • In System.Drawing funktioniert die HighQualityBicubic-Skalierungskonvertierung nur mit dem PRGBA-Format. In fast allen Szenarien bedeutet dies eine zusätzliche Kopie des Bildes. Dies verbraucht nicht nur (erheblich) mehr zusätzlichen Speicher, sondern verbrennt auch die Prozessorzyklen zum Konvertieren und Verarbeiten des zusätzlichen Alphakanals.
  • Selbst wenn das Bild in seinem ursprünglichen Format vorliegt, führt die HighQualityBicubic-Skalierungskonvertierung ungefähr viermal mehr Berechnungen durch, als für die Erzielung der korrekten Konvertierungsergebnisse erforderlich sind.

Diese Tatsachen führen zu einer erheblichen Anzahl verschwendeter CPU-Zyklen. In einer trüben Umgebung mit einer Zahlung pro Minute trägt dies direkt zu den Hosting-Kosten bei. Und natürlich wird Ihre Reaktionszeit leiden.

Und denken Sie daran, dass zusätzlicher Strom verbraucht und Wärme erzeugt wird. Ihre Verwendung von System.Drawing für Bildverarbeitungsaufgaben wirkt sich direkt auf die globale Erwärmung aus. Du bist ein Monster.

Grund Nr. 5: Die Bildverarbeitung ist täuschend komplex


Abgesehen von der Leistung verhindert System.Drawing in vielerlei Hinsicht, dass das Bild korrekt verarbeitet wird. Das Verwenden von System.Drawing bedeutet, entweder mit einer falschen Ausgabe zu leben oder alles über das ICC-Profil, die Farbquantisierung, die Exif-Ausrichtung, die Korrektur und viele andere spezifische Dinge zu lernen. Dies ist ein Kaninchenbau, den die meisten Entwickler weder erforschen noch erforschen möchten.

Bibliotheken wie ImageResizer und ImageProcessor haben eine Menge Fans gewonnen, die sich um einige dieser Details kümmern, aber seien Sie vorsichtig, sie haben System.Drawing im Inneren und sie kommen mit all dem Gepäck, das ich in diesem Artikel ausführlich beschrieben habe.

Bonusgrund: Sie können es besser machen


Wenn Sie wie ich irgendwann in Ihrem Leben eine Brille tragen mussten, erinnern Sie sich wahrscheinlich, wie Sie sie zum ersten Mal aufgesetzt haben. Ich dachte, ich sehe normal und wenn ich richtig schaue, ist alles ziemlich klar. Aber dann setzte ich diese Brille auf und die Welt wurde viel detaillierter, als ich es mir hätte vorstellen können.

System.Drawing ist ähnlich. Es ist richtig, wenn Sie die Einstellungen richtig ausfüllen , aber Sie werden überrascht sein, wie viel besser Ihre Bilder aussehen können, wenn Sie die besten Dienstprogramme verwenden.

Ich lasse dies hier nur als Beispiel. Dies ist die beste System.Drawing-Funktion im Vergleich zu den Standardeinstellungen von MagicScaler. Vielleicht profitiert Ihre Bewerbung davon, Punkte zu bekommen ...

Gdi:

Bild

MagicScaler:

Bild
Foto von Jakob Owens

Schauen Sie sich um, suchen Sie nach Alternativen und beenden Sie im Namen der Kätzchenliebe die Verwendung von System.Drawing in ASP.NET

Source: https://habr.com/ru/post/de482274/


All Articles