5 raisons pour lesquelles vous devriez cesser d'utiliser System.Drawing dans ASP.NET

Bonjour, Habr! Je vous présente la traduction de l'article «5 raisons pour lesquelles vous devez cesser d'utiliser System.Drawing depuis ASP.NET» .

image

Eh bien, ils l'ont fait. L'équipe corefx a finalement accepté de nombreuses demandes et inclus System.Drawing dans .NET Core. (article original daté de juillet 2017)

Le prochain package System.Drawing.Common contiendra la plupart des fonctionnalités System.Drawing du .NET Framework complet et est destiné à être utilisé comme option de compatibilité pour ceux qui souhaitent migrer vers .NET Core mais ne peuvent pas le faire en raison de dépendances. De ce point de vue, Microsoft fait ce qu'il faut. La friction doit être réduite car l'adoption de .Net Core est un objectif plus valable.

D'un autre côté, System.Drawing est l'un des domaines les plus pauvres et les plus défavorisés du .Net Framework et beaucoup d'entre nous espéraient que la mise en œuvre de .NET Core signifierait la mort lente de System.Drawing. Et avec cette mort devrait être l'occasion de faire quelque chose de mieux.

Par exemple, l'équipe Mono a créé un wrapper compatible .NET pour la bibliothèque graphique multiplateforme Skia de Google, appelée SkiaSharp . Pour simplifier l'installation, Nuget a parcouru un long chemin dans la prise en charge des bibliothèques natives pour chaque plate-forme. Skia est assez complet et ses performances font System.Drawing.

L'équipe ImageSharp a également fait un excellent travail en répétant une grande partie de la fonctionnalité System.Drawing, mais avec la meilleure API et une implémentation 100% C #. Ils ne sont toujours pas prêts pour une exploitation productive, mais il semble qu'ils soient déjà assez proches de cela. Un petit avertissement à propos de cette bibliothèque, car nous parlons d'une utilisation dans les applications serveur: maintenant, dans la configuration par défaut, Parallel.For est utilisé à l'intérieur pour accélérer certaines opérations, ce qui signifie que plus de workflows du pool ASP.NET seront utilisés, éventuellement En conséquence, la réduction du débit global de l'application . J'espère que ce comportement sera revu avant la sortie, mais même maintenant, il suffit de changer une ligne de la configuration pour la rendre plus adaptée à une utilisation sur le serveur.

Dans tous les cas, si vous dessinez, tracez ou restituez du texte en images dans une application sur le serveur, vous devriez sérieusement envisager de modifier System.Drawing en quelque chose, que vous passiez ou non à .NET Core.

Pour ma part, j'ai assemblé un pipeline de traitement d'image hautes performances pour .NET et .NET Core, qui fournit une qualité d'image que System.Drawing ne peut pas fournir, et le fait dans une architecture hautement évolutive conçue spécifiquement pour une utilisation sur le serveur. Jusqu'à présent, ce n'est que pour Windows, cependant, la multiplateforme est dans les plans. Si vous utilisez System.Drawing (ou autre chose) pour redimensionner les images sur le serveur, il est préférable de considérer MagicScaler comme un remplacement.

Mais une résurrection de System.Drawing, qui facilite la transition pour certains développeurs, est susceptible de tuer l'essentiel de l'élan reçu par ces projets, car les développeurs ont été forcés de chercher des alternatives. Malheureusement, dans l'écosystème .NET, les bibliothèques et packages Microsoft gagneront toujours, quelle que soit la qualité des alternatives.

Ce message est une tentative de corriger certaines erreurs de calcul de System.Drawing dans l'espoir que les développeurs exploreront des alternatives même si System.Drawing reste une option.

Je vais commencer par la clause de non - responsabilité souvent citée dans la documentation System.Drawing. Ce rejet a été soulevé plusieurs fois dans une discussion sur Github lors de la discussion sur System.Drawing.Common .
«Les classes avec l'espace de noms System.Drawing ne sont pas prises en charge pour une utilisation dans les services Windows ou ASP.NET. Tenter d'utiliser ces classes avec ces types d'applications peut entraîner des problèmes inattendus, tels qu'une baisse des performances du serveur et des erreurs d'exécution. "

Comme beaucoup d'entre vous, j'ai lu cet avertissement il y a très longtemps, puis je l'ai ignoré et j'ai toujours utilisé System.Drawing dans mon application ASP.NET. Pourquoi? Parce que j'aime le danger. Soit cela, soit aucune autre option viable n'a été trouvée. Et tu sais quoi? Il ne s'est rien passé de mal. Je n'aurais probablement pas dû dire cela, mais je parie que beaucoup d'entre vous ont vécu la même chose. Alors pourquoi ne pas continuer à utiliser System.Drawing ou des bibliothèques basées sur lui?

Raison n ° 1: Descripteurs GDI


Si vous avez déjà rencontré des problèmes lors de l'utilisation de System.Drawing sur un serveur, c'est très probablement le cas. S'il n'est pas testé, c'est l'une des causes les plus probables.

System.Drawing est pour la plupart une enveloppe mince autour de l'API Windows GDI +. De nombreux objets System.Drawing sont pris en charge par les descripteurs GDI , et ils ont une limite quantitative sur le processeur et la session utilisateur. Si ce seuil est atteint, vous obtiendrez une exception «Mémoire insuffisante» et / ou une erreur «générique» GDI +.

Le problème est que dans .NET, la récupération de place et l'arrêt du processus peuvent retarder la publication de ces descripteurs au moment où vous atteignez la limite, même sous une charge légère. Si vous oubliez (ou ne saviez pas ce dont vous avez besoin) d'appeler Dispose () sur un objet qui contient de tels descripteurs, vous risquez fort de rencontrer de telles erreurs dans votre environnement. Et comme la plupart des bogues liés aux limitations ou aux fuites de ressources, cette situation sera probablement testée avec succès et vous piquera en fonctionnement productif. Naturellement, cela se produit lorsque votre application est sous la plus grande charge, de sorte que le nombre maximum d'utilisateurs découvre votre honte.

Les restrictions sur le processeur et sur la session utilisateur dépendent de la version du système d'exploitation, et la restriction sur le processeur est personnalisable. Mais la version n'a pas d'importance, car Les descripteurs GDI sont représentés en interne par le type de données USHORT, il y a donc une limite stricte de 65 536 descripteurs par session utilisateur, et même une application bien écrite risque d'atteindre cette limite sous une charge suffisante. Lorsque vous pensez qu'un serveur plus puissant vous permettra de servir de plus en plus d'utilisateurs en parallèle sur une seule instance, ce risque devient plus réel. Et vraiment, qui veut créer un logiciel avec une limite bien connue d'évolutivité?

Raison n ° 2: accès simultané


GDI + a toujours eu des problèmes de simultanéité, bien que beaucoup d'entre eux étaient liés à des modifications architecturales dans Windows7 / Windows Server 2008 R2 , vous en voyez toujours certains dans les nouvelles versions. Le plus notable est le verrouillage de processus organisé par GDI + lors de l'opération DrawImage (). Si vous redimensionnez des images sur le serveur à l'aide de System.Drawing (ou des bibliothèques qui l'enveloppent), la méthode DrawImage () est probablement la base de ce code.

De plus, lorsque vous effectuez plusieurs appels à DrawImage () en même temps, tous seront bloqués jusqu'à ce qu'ils soient tous exécutés. Même si le temps de réponse n'est pas un problème pour vous (pourquoi pas? Détestez-vous vos utilisateurs?) Gardez à l'esprit que toutes les ressources mémoire associées à ces demandes et tous les descripteurs GDI détenus par les objets associés à ces demandes sont liés à l'exécution. En fait, cela ne prend pas trop de charge sur le serveur pour commencer à causer des problèmes.

Bien sûr, il existe des solutions de contournement pour ce problème particulier. Par exemple, certains développeurs créent un processus externe pour chaque opération DrawImage (). Mais en fait, une telle solution de contournement ne fait qu'ajouter une fragilité supplémentaire, ce que vous n'auriez vraiment pas dû faire.

Raison n ° 3: la mémoire


Considérez un gestionnaire ASP.NET qui génère un graphique. Il devrait faire quelque chose comme ça:

  1. Créer un bitmap comme toile
  2. Dessinez plusieurs formes sur une image bitmap à l'aide de stylos et / ou de pinceaux
  3. Dessinez du texte en utilisant une ou plusieurs polices
  4. Enregistrer le bitmap au format PNG dans MemoryStream

Disons que le graphique mesure 600 par 400 points. Il s'agit d'un total de 240 000 points, multiplié par 4 octets pour un point pour le format RGBA par défaut, totalisant 960 000 octets pour un bitmap, plus un peu pour les objets de dessin et un tampon de sauvegarde. Soit 1 Mo pour toute la demande. Très probablement, vous n'obtiendrez pas de problèmes de mémoire pour un tel scénario, et si vous rencontrez quelque chose, vous aurez très probablement une limite sur le nombre de descripteurs, que j'ai mentionné plus tôt, car les images, les pinceaux, les stylos et les polices ont leurs propres descripteurs.

Le vrai problème survient lorsque System.Drawing est utilisé pour les tâches de création d'image. System.Drawing est principalement une bibliothèque graphique, et les bibliothèques graphiques sont généralement toutes construites autour de l'idée que tout est un bitmap en mémoire. C'est génial pendant que vous pensez aux petites choses. Mais les images peuvent être très grandes, et elles grossissent chaque jour, car les appareils photo avec beaucoup de mégapixels sont de moins en moins chers.

Si vous adoptez l'approche naïve de System.Drawing pour la création d'images, vous obtenez quelque chose comme ceci pour le gestionnaire de redimensionnement:

  1. Créez un bitmap en tant que canevas pour l'image de destination.
  2. Chargez l'image d'origine dans un autre bitmap.
  3. Appelez DrawImage () avec le paramètre "image-source" pour l'image de destination, en utilisant le redimensionnement.
  4. Enregistrez le bitmap cible au format JPEG dans le flux de mémoire.

Supposons que l'image cible soit de 600 x 400, comme dans l'exemple précédent, puis encore une fois, nous avons 1 Mo pour l' image cible et le flux de mémoire. Mais supposons que quelqu'un ait téléchargé une image de 24 mégapixels à partir de ses nouveaux reflex numériques sophistiqués, alors nous avons besoin de 6000x4000 pixels avec 3 octets pour chacun (72 Mo) pour le bitmap source décodé au format RVB. Et nous utiliserons le rééchantillonnage HighQualityBicubic de System.Drawing, car il ne semble que bon. Ensuite, nous devons prendre en compte les autres points 6000x4000 avec 4 octets chacun, pour la conversion PRGBA qui se produit à l'intérieur de la méthode appelée , en ajoutant 96 Mo supplémentaires de mémoire utilisée. Au total, 169 Mo (!) Sont obtenus pour une demande de conversion d'une seule image.

Imaginez maintenant que plusieurs utilisateurs fassent de telles choses. N'oubliez pas que les demandes sont bloquées jusqu'à ce qu'elles soient toutes entièrement exécutées. Combien de temps faut-il pour manquer de mémoire? Et même si vous ne craignez pas d'épuiser complètement tout ce qui est disponible, n'oubliez pas qu'il existe de nombreuses façons de mieux utiliser la mémoire de votre serveur que de conserver un tas de pixels. Tenez compte de l'effet de la pression de la mémoire sur d'autres parties de l'application / du système:

  1. Le cache ASP.NET peut commencer à vider les éléments coûteux à recréer
  2. Le ramasse-miettes démarrera plus souvent, ralentissant l'application
  3. Le cache du noyau IIS ou le cache du système de fichiers Windows peut supprimer des éléments utiles
  4. Le pool d'applications peut dépasser la limite de mémoire et peut être redémarré
  5. Windows peut commencer à échanger de la mémoire sur le disque, ce qui ralentit l'ensemble du système

Tu ne veux vraiment rien de tout ça?

Les bibliothèques conçues spécifiquement pour les tâches de traitement d'image abordent ce problème d'une manière complètement différente. Ils n'ont pas besoin de charger l'intégralité de l'image source ou cible en mémoire. Si vous n'allez pas dessiner dessus, vous n'avez pas besoin d'un canevas / bitmap. Cela se fait plus comme ceci:

  1. Créer un flux pour l'encodeur JPEG de l'image cible
  2. Chargez une ligne de l'image d'origine et compressez-la horizontalement
  3. Répétez autant de fois que nécessaire pour former une ligne pour le fichier cible
  4. Compressez les lignes résultantes verticalement
  5. Répétez à partir de l'étape 2 jusqu'à ce que toutes les lignes du fichier source soient traitées.

En utilisant cette méthode, la même image peut être traitée en utilisant 1 Mo de mémoire au total, et même des images beaucoup plus grandes nécessiteront une légère augmentation des frais généraux.

Je ne connais qu'une seule bibliothèque .NET optimisée par ce principe, et je vais vous donner un indice: ce n'est pas System.Drawing.

Raison n ° 4: CPU


Un autre effet secondaire de System.Drawing étant plus orienté graphiquement qu'orienté image est que DrawImage () est assez inefficace en termes d'utilisation du CPU. J'ai couvert cela en détail dans un article précédent , mais cette discussion peut être résumée par les faits suivants:

  • Dans System.Drawing, la conversion d'échelle HighQualityBicubic ne fonctionne qu'avec le format PRGBA. Dans presque tous les scénarios, cela signifie une copie supplémentaire de l'image. Non seulement cela utilise (considérablement) plus de mémoire supplémentaire, mais il brûle également les cycles du processeur pour convertir et traiter le canal alpha supplémentaire.
  • Même après que l'image est dans son format natif, la conversion d'échelle HighQualityBicubic effectue environ 4 fois plus de calculs que nécessaire pour obtenir les résultats de conversion corrects.

Ces faits ajoutent une quantité importante de cycles CPU gaspillés. Dans un environnement nuageux avec paiement à la minute, cela contribue directement au coût de l'hébergement. Et bien sûr, votre temps de réponse en souffrira.

Et pensez au fait que de l'électricité supplémentaire sera dépensée et de la chaleur produite. Votre utilisation de System.Drawing pour les tâches de traitement d'image affecte directement le réchauffement climatique. Tu es un monstre.

Raison n ° 5: le traitement d'image est d'une complexité trompeuse


Mis à part les performances, System.Drawing empêche à plusieurs égards le traitement correct de l'image. Utiliser System.Drawing signifie vivre avec une sortie incorrecte ou tout apprendre sur le profil ICC, la quantification des couleurs, l'orientation exif, la correction et bien d'autres choses spécifiques. Il s'agit d'un terrier de lapin que la plupart des développeurs n'ont ni le temps ni l'envie d'explorer.

Les bibliothèques telles que ImageResizer et ImageProcessor ont gagné beaucoup de fans, en prenant soin de certains de ces détails, mais attention, elles ont System.Drawing à l'intérieur, et elles accompagnent tous les bagages que j'ai décrits en détail dans cet article.

Raison du bonus: vous pouvez faire mieux


Si vous, comme moi, avez dû porter des lunettes à un moment de votre vie, vous vous souvenez probablement comment c'était la première fois que vous les mettiez. Je pensais que je voyais normalement, et si je plisse les yeux correctement, alors tout sera assez clair. Mais ensuite j'ai mis ces lunettes, et le monde est devenu beaucoup plus détaillé que je n'aurais pu l'imaginer.

System.Drawing est à peu près la même chose. Cela fait la bonne chose si vous remplissez correctement les paramètres , mais vous serez surpris de voir à quel point vos images peuvent être meilleures si vous utilisez les meilleurs utilitaires.

Je vais juste laisser cela ici à titre d'exemple. C'est le meilleur système que System.Drawing puisse faire par rapport aux paramètres MagicScaler par défaut. Peut-être que votre application bénéficiera de l'obtention de points ...

Gdi:

image

MagicScaler:

image
Photo de Jakob Owens

Jetez un coup d'œil, explorez des alternatives et s'il vous plaît, au nom de l'amour pour les chatons, arrêtez d'utiliser System.Drawing dans ASP.NET

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


All Articles