5 razões pelas quais você deve parar de usar o System.Drawing no ASP.NET

Olá Habr! Apresento a você a tradução do artigo "5 razões pelas quais você deve parar de usar o System.Drawing from ASP.NET" .

imagem

Bem, eles fizeram isso. A equipe corefx finalmente concordou com várias solicitações e incluiu System.Drawing no .NET Core. (artigo original de julho de 2017)

O próximo pacote System.Drawing.Common conterá a maior parte da funcionalidade System.Drawing do .NET Framework completo e deve ser usado como uma opção de compatibilidade para quem deseja migrar para o .NET Core, mas não pode fazer isso devido a dependências. Nesta perspectiva, a Microsoft está fazendo a coisa certa. O atrito precisa ser reduzido, pois a adoção do .Net Core é uma meta que vale a pena.

Por outro lado, o System.Drawing é uma das áreas mais pobres e mais carenciadas do .Net Framework e muitos de nós esperávamos que a implementação do .NET Core significasse a morte lenta do System.Drawing. E junto com essa morte deve haver uma oportunidade de fazer algo melhor.

Por exemplo, a equipe Mono criou um invólucro compatível com .NET para a biblioteca de gráficos Skia de plataforma cruzada do Google, chamada SkiaSharp . Para simplificar a instalação, o Nuget já percorreu um longo caminho no suporte de bibliotecas nativas para cada plataforma. Skia é bastante completo e seu desempenho faz System.Drawing.

A equipe do ImageSharp também fez um ótimo trabalho repetindo grande parte da funcionalidade System.Drawing, mas com a melhor API e implementação 100% C #. Eles ainda não estão prontos para a exploração produtiva, mas parece que já estão perto o suficiente disso. Um pequeno aviso sobre esta biblioteca, já que estamos falando sobre o uso em aplicativos de servidor: agora, na configuração padrão, o Parallel.For é usado para acelerar algumas operações, o que significa que mais fluxos de trabalho do pool do ASP.NET serão usados, eventualmente. Como resultado, reduzindo a taxa de transferência geral do aplicativo . Espero que esse comportamento seja revisto antes do lançamento, mas mesmo agora é suficiente alterar uma linha da configuração para torná-la mais adequada para uso no servidor.

De qualquer forma, se você estiver desenhando, plotando ou renderizando texto em imagens em um aplicativo no servidor, considere seriamente alterar o System.Drawing para qualquer coisa, independentemente de você alternar para o .NET Core ou não.

Da minha parte, montei um pipeline de processamento de imagem de alto desempenho para .NET e .NET Core, que fornece qualidade de imagem que o System.Drawing não pode fornecer e o faz em uma arquitetura altamente escalável projetada especificamente para uso no servidor. Até agora, é apenas para Windows, no entanto, a plataforma cruzada está nos planos. Se você usar System.Drawing (ou qualquer outra coisa) para redimensionar imagens no servidor, é melhor considerar o MagicScaler como um substituto.

Mas uma ressurreição do System.Drawing, que facilita a transição para alguns desenvolvedores, provavelmente matará a maior parte do momento que esses projetos receberam, pois os desenvolvedores foram forçados a procurar alternativas. Infelizmente, no ecossistema .NET, as bibliotecas e pacotes da Microsoft sempre vencerão, não importa quão superiores sejam as alternativas.

Esta postagem é uma tentativa de corrigir alguns erros de cálculo do System.Drawing na esperança de que os desenvolvedores explorem alternativas, mesmo que o System.Drawing continue sendo uma opção.

Começarei com o aviso de isenção de responsabilidade frequentemente mencionado na documentação System.Drawing. Essa rejeição foi levantada algumas vezes em uma discussão no Github ao discutir o System.Drawing.Common .
“Classes com o espaço para nome System.Drawing não são suportadas para uso nos serviços Windows ou ASP.NET. Tentar usar essas classes com esses tipos de aplicativos pode causar problemas inesperados, como diminuição do desempenho do servidor e erros de tempo de execução. "

Como muitos de vocês, li esse aviso há muito tempo e pulei e ainda usei o System.Drawing no meu aplicativo ASP.NET. Porque Porque eu amo o perigo. Ou isso, ou nenhuma outra opção viável foi encontrada. E você sabe o que? Nada de ruim aconteceu. Provavelmente eu não deveria ter dito isso, mas aposto que muitos de vocês experimentaram a mesma coisa. Então, por que não continuar usando o System.Drawing ou bibliotecas baseadas nele?

Razão # 1: Descritores GDI


Se você já teve problemas ao usar o System.Drawing em um servidor, provavelmente é esse o caso. Se não testado, essa é uma das causas possíveis mais prováveis.

O System.Drawing geralmente é um invólucro fino em torno da API do Windows GDI +. Muitos objetos System.Drawing são suportados por descritores de GDI e possuem um limite quantitativo na sessão do processador e do usuário. Se esse limite for atingido, você receberá uma exceção "Memória insuficiente" e / ou erro 'genérico' do GDI +.

O problema é que, no .NET, a coleta de lixo e o encerramento do processo podem atrasar o lançamento desses descritores quando você atingir o limite, mesmo com pouca carga. Se você esquecer (ou não sabia do que precisa) chamar Dispose () em um objeto que contém esses descritores, você corre o risco de encontrar esses erros em seu ambiente. E, como a maioria dos bugs relacionados a limitações ou vazamentos de recursos, provavelmente essa situação será testada com sucesso e o afetará na operação produtiva. Naturalmente, isso acontecerá quando seu aplicativo estiver sob a maior carga, para que o número máximo de usuários descubra sua vergonha.

As restrições no processador e na sessão do usuário dependem da versão do sistema operacional, e a restrição no processador é personalizável. Mas a versão não importa, porque Os descritores de GDI são representados internamente pelo tipo de dados USHORT, portanto, há um limite estrito de 65.536 descritores por sessão do usuário e até mesmo um aplicativo bem escrito corre o risco de atingir esse limite com carga suficiente. Quando você acredita que um servidor mais poderoso permitirá atender mais e mais usuários em paralelo em uma instância, esse risco se torna mais real. E realmente, quem quer criar software com um conhecido limite rígido de escalabilidade?

Razão # 2: Concorrência


O GDI + sempre teve problemas com a simultaneidade, embora muitos deles estivessem relacionados a alterações arquiteturais no Windows7 / Windows Server 2008 R2 , você ainda vê alguns deles em novas versões. O mais notável é o bloqueio do processo organizado pelo GDI + durante a operação DrawImage (). Se você redimensionar imagens no servidor usando System.Drawing (ou as bibliotecas que o envolvem), o método DrawImage () provavelmente será a base desse código.

Além disso, ao fazer várias chamadas para DrawImage () ao mesmo tempo, todas elas serão bloqueadas até que todas sejam executadas. Mesmo que o tempo de resposta não seja um problema para você (por que não? Você odeia os usuários?) Lembre-se de que quaisquer recursos de memória associados a essas solicitações e todos os descritores de GDI mantidos pelos objetos associados a essas solicitações estão ligados ao tempo de execução. De fato, não é preciso muita carga no servidor para começar a causar problemas.

Obviamente, existem soluções alternativas para esse problema específico. Por exemplo, alguns desenvolvedores criam um processo externo para cada operação DrawImage (). Mas, de fato, essa solução alternativa adiciona mais fragilidade, o que você realmente não deveria ter feito.

Razão # 3: Memória


Considere um manipulador do ASP.NET que gera um gráfico. Ele deveria fazer algo assim:

  1. Criar um bitmap como uma tela
  2. Desenhar várias formas em um bitmap usando canetas e / ou pincéis
  3. Desenhar texto usando uma ou mais fontes
  4. Salvar bitmap como PNG no MemoryStream

Digamos que o gráfico mede 600 por 400 pontos. É um total de 240.000 pontos, multiplicado por 4 bytes para um ponto no formato RGBA padrão, totalizando 960.000 bytes para um bitmap, mais um pouco para objetos de desenho e um buffer de salvamento. Deixe 1mb para toda a solicitação. Provavelmente, você não terá problemas de memória para esse cenário e, se encontrar alguma coisa, provavelmente terá um limite no número de descritores, que mencionei anteriormente, já que imagens, pincéis, canetas e fontes têm seus próprios descritores.

O verdadeiro problema ocorre quando o System.Drawing é usado para tarefas de criação de imagens. O System.Drawing é principalmente uma biblioteca de gráficos, e as bibliotecas de gráficos geralmente são criadas com base na idéia de que tudo é um bitmap na memória. Isso é ótimo enquanto você pensa sobre as pequenas coisas. Mas as imagens podem ser muito grandes e aumentam a cada dia, porque câmeras com muitos megapixels estão constantemente ficando mais baratas.

Se você usar a abordagem ingênua do System.Drawing para criar imagens, obterá algo assim para o manipulador de redimensionamento:

  1. Crie um bitmap como uma tela para a imagem de destino.
  2. Carregue a imagem original em outro bitmap.
  3. Chame DrawImage () com o parâmetro "image-source" para a imagem de destino, usando o redimensionamento.
  4. Salve o bitmap de destino no formato JPEG no fluxo de memória.

Suponha que a imagem de destino tenha 600x400, como no exemplo anterior, novamente temos 1 MB para a imagem de destino e o fluxo de memória. Mas vamos supor que alguém tenha carregado uma imagem de 24 megapixels de suas novas DSLRs sofisticadas, então precisamos de 6000x4000 pixels com 3 bytes para cada (72 MB) para o bitmap de origem decodificado no formato RGB. E usaremos a reamostragem HighQualityBicubic do System.Drawing, porque ela apenas parece boa. Em seguida, precisamos levar em consideração os outros pontos 6000x4000 com 4 bytes cada, para a conversão PRGBA que ocorre dentro do método chamado , adicionando 96mb adicionais de memória usada. No total, 169mb (!) São obtidos para uma solicitação para converter uma única imagem.

Agora imagine que você tem mais de um usuário fazendo essas coisas. Agora lembre-se de que as solicitações são bloqueadas até que todas sejam completamente executadas. Quanto tempo leva para ficar sem memória? E mesmo que você não se preocupe com o esgotamento completo de tudo o que estiver disponível, lembre-se de que existem várias maneiras de usar melhor a memória do seu servidor do que armazenar vários pixels. Considere o efeito da pressão da memória em outras partes do aplicativo / sistema:

  1. O cache do ASP.NET pode começar a liberar itens caros para recriar
  2. O coletor de lixo será iniciado com mais frequência, diminuindo a velocidade do aplicativo
  3. O cache do kernel do IIS ou o cache do sistema de arquivos do Windows podem remover elementos úteis
  4. O pool de aplicativos pode exceder o limite de memória e pode ser reiniciado
  5. O Windows pode começar a trocar memória para disco, diminuindo a velocidade do sistema inteiro

Você realmente não quer nada disso?

As bibliotecas projetadas especificamente para tarefas de processamento de imagem abordam esse problema de uma maneira completamente diferente. Eles não precisam carregar a fonte inteira ou a imagem de destino na memória. Se você não quiser desenhar, não precisa de um canvas / bitmap. Isso é feito mais ou menos assim:

  1. Criar fluxo para o codificador JPEG da imagem de destino
  2. Carregue uma linha da imagem original e comprima-a horizontalmente
  3. Repita quantas vezes forem necessárias para formar uma linha para o arquivo de destino
  4. Comprima as linhas resultantes verticalmente
  5. Repita da etapa 2 até que todas as linhas do arquivo de origem sejam processadas.

Usando esse método, a mesma imagem pode ser processada usando 1 MB de memória no total, e imagens muito maiores exigirão um ligeiro aumento na sobrecarga.

Conheço apenas uma biblioteca .NET otimizada por esse princípio e darei uma dica: esse não é o System.Drawing.

Razão # 4: CPU


Outro efeito colateral do System.Drawing, sendo mais orientado graficamente do que orientado a imagens, é que DrawImage () é bastante ineficiente em termos de uso da CPU. Eu cobri isso em detalhes em um post anterior , mas essa discussão pode ser resumida pelos seguintes fatos:

  • No System.Drawing, a conversão de escala HighQualityBicubic funciona apenas com o formato PRGBA. Em quase todos os cenários, isso significa uma cópia extra da imagem. Além de usar (significativamente) mais memória adicional, ele também queima os ciclos do processador para converter e processar o canal alfa extra.
  • Mesmo depois que a imagem está em seu formato nativo, a conversão na escala HighQualityBicubic realiza cerca de quatro vezes mais cálculos do que o necessário para obter os resultados de conversão corretos.

Esses fatos adicionam uma quantidade significativa de ciclos de CPU desperdiçados. Em um ambiente nublado com pagamento por minuto, isso contribui diretamente para o custo de hospedagem. E, claro, seu tempo de resposta sofrerá.

E pense no fato de que eletricidade adicional será gasta e calor gerado. Seu uso do System.Drawing para tarefas de processamento de imagem afeta diretamente o aquecimento global. Você é um monstro.

Razão # 5: o processamento da imagem é enganosamente complexo


Além do desempenho, o System.Drawing evita o processamento correto da imagem. Usar o System.Drawing significa viver com saída incorreta ou aprender tudo sobre o perfil ICC, quantização de cores, orientação exif, correção e muitas outras coisas específicas. Esta é uma toca de coelho, que a maioria dos desenvolvedores não tem tempo nem vontade de explorar.

Bibliotecas como ImageResizer e ImageProcessor conquistaram muitos fãs, cuidando de alguns desses detalhes, mas tenha cuidado, pois possuem System.Drawing por dentro e eles vêm com toda a bagagem que descrevi em detalhes neste artigo.

Motivo do bônus: você pode fazer melhor


Se você, como eu, teve que usar óculos em algum momento da sua vida, provavelmente se lembra de como foi a primeira vez que os colocou. Eu pensei que estava vendo normalmente, e se eu apertar os olhos corretamente, tudo ficará bem claro. Mas então coloquei esses óculos e o mundo ficou muito mais detalhado do que eu poderia imaginar.

System.Drawing é o mesmo. Ele faz a coisa certa se você preencher as configurações corretamente , mas você ficará surpreso com a aparência das imagens, se usar os melhores utilitários.

Vou deixar isso aqui como um exemplo. Este é o melhor que o System.Drawing pode fazer em comparação com as configurações padrão do MagicScaler. Talvez seu aplicativo se beneficie da obtenção de pontos ...

Gdi:

imagem

MagicScaler:

imagem
Foto de Jakob Owens

Dê uma olhada, explore alternativas e, em nome do amor pelos gatinhos, pare de usar o System.Drawing no ASP.NET

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


All Articles