Compartilhar via


Domínios de aplicativo

Observação

Este artigo é específico do .NET Framework. Ele não se aplica a implementações mais recentes do .NET, incluindo o .NET 6 e versões posteriores.

Os sistemas operacionais e os ambientes de runtime normalmente fornecem alguma forma de isolamento entre aplicativos. Por exemplo, o Windows usa processos para isolar aplicativos. Esse isolamento é necessário para garantir que o código em execução em um aplicativo não possa afetar negativamente outros aplicativos não relacionados.

Os domínios de aplicativo fornecem um limite de isolamento para segurança, confiabilidade e controle de versão e para descarregar assemblies. Os domínios de aplicativo normalmente são criados por hosts de runtime, que são responsáveis por inicializar o common language runtime antes de um aplicativo ser executado.

Os benefícios de isolar aplicativos

Historicamente, os limites de processo têm sido usados para isolar aplicativos em execução no mesmo computador. Cada aplicativo é carregado em um processo separado, que isola o aplicativo de outros aplicativos em execução no mesmo computador.

Os aplicativos são isolados porque os endereços de memória são relativos ao processo; um ponteiro de memória passado de um processo para outro não pode ser usado de forma significativa no processo de destino. Além disso, você não pode fazer chamadas diretas entre dois processos. Em vez disso, você deve usar proxies, que fornecem um nível de intermediação.

O código gerenciado deve ser passado por um processo de verificação antes de ser executado (a menos que o administrador tenha concedido permissão para ignorar a verificação). O processo de verificação determina se o código pode tentar acessar endereços de memória inválidos ou executar alguma outra ação que possa fazer com que o processo em que ele está em execução não funcione corretamente. Diz-se que o código que passa pelo teste de verificação é chamado de fortemente tipado. A capacidade de verificar o código como type-safe permite que o common language runtime forneça um nível tão grande de isolamento quanto o limite do processo, a um custo de desempenho muito menor.

Os domínios de aplicativo fornecem uma unidade de processamento mais segura e versátil que o common language runtime pode usar para fornecer isolamento entre aplicativos. Você pode executar vários domínios de aplicativo em um único processo com o mesmo nível de isolamento que existiria em processos separados, mas sem incorrer na sobrecarga adicional de fazer chamadas entre processos ou alternar entre processos. A capacidade de executar vários aplicativos em um único processo aumenta drasticamente a escalabilidade do servidor.

Isolar aplicativos também é importante para a segurança do aplicativo. Por exemplo, você pode executar controles de vários aplicativos Web em um único processo de navegador de forma que os controles não possam acessar os dados e os recursos uns dos outros.

O isolamento fornecido pelos domínios do aplicativo tem os seguintes benefícios:

  • Falhas em um aplicativo não podem afetar outros aplicativos. Como o código de tipo seguro não pode causar falhas de memória, o uso de domínios de aplicativo garante que o código em execução em um domínio não possa afetar outros aplicativos no processo.

  • Aplicativos individuais podem ser interrompidos sem interromper todo o processo. O uso de domínios de aplicativo permite descarregar o código em execução em um único aplicativo.

    Observação

    Você não pode descarregar assemblies ou tipos individuais. Somente um domínio completo pode ser descarregado.

  • O código em execução em um aplicativo não pode acessar diretamente o código ou os recursos de outro aplicativo. O common language runtime impõe esse isolamento impedindo chamadas diretas entre objetos em diferentes domínios de aplicativo. Os objetos que passam entre domínios são copiados ou acessados por proxy. Se o objeto for copiado, a chamada para o objeto será local. Ou seja, tanto o chamador quanto o objeto que está sendo referenciado estão no mesmo domínio do aplicativo. Se o objeto for acessado por meio de um proxy, a chamada para o objeto será remota. Nesse caso, o chamador e o objeto que está sendo referenciado estão em domínios de aplicativo diferentes. As chamadas entre domínios usam a mesma infraestrutura de chamada remota que as chamadas entre dois processos ou entre dois computadores. Dessa forma, os metadados do objeto que está sendo referenciado devem estar disponíveis para ambos os domínios de aplicação, permitindo que a chamada de método seja compilada pelo JIT corretamente. Se o domínio de chamada não tiver acesso aos metadados do objeto que está sendo chamado, a compilação poderá falhar com uma exceção do tipo FileNotFoundException. Para obter mais informações, consulte Objetos Remotos. O mecanismo para determinar como os objetos podem ser acessados entre domínios é determinado pelo objeto. Para obter mais informações, consulte System.MarshalByRefObject.

  • O comportamento do código é delimitado pelo aplicativo no qual é executado. Em outras palavras, o domínio do aplicativo fornece definições de configuração, como as políticas de versão do aplicativo, o local de qualquer assembly remoto acessado e informações sobre onde localizar assemblies carregados no domínio.

  • As permissões concedidas ao código podem ser controladas pelo domínio do aplicativo no qual o código está em execução.

Domínios de aplicativo e assemblies

Esta seção descreve a relação entre domínios de aplicativo e assemblies. Você deve carregar um assembly em um domínio de aplicativo antes de poder executar o código que ele contém. Executar um aplicativo típico faz vários assemblies serem carregados em um domínio de aplicativo.

A maneira como um assembly é carregado determina se seu código compilado just-in-time (JIT) pode ser compartilhado por vários domínios de aplicativo no processo e se o assembly pode ser descarregado do processo.

  • Se um assembly for carregado em domínio neutro, todos os domínios de aplicativo que compartilharem o mesmo conjunto de concessões de segurança poderão compartilhar o mesmo código compilado por JIT, o que reduz a memória exigida pelo aplicativo. No entanto, o assembly jamais pode ser descarregado do processo.

  • Se um assembly não for carregado em domínio neutro, ele deverá ser compilado por JIT em cada domínio de aplicativo no qual ele for carregado. No entanto, o assembly pode ser descarregado do processo descarregando-se todos os domínios de aplicativo em que ele está carregado.

O host de runtime decidirá se é necessário carregar os assemblies como neutros com relação ao domínio quando ele carregar o runtime em um processo. Para aplicativos gerenciados, aplique o LoaderOptimizationAttribute atributo ao método de ponto de entrada para o processo e especifique um valor da enumeração associada LoaderOptimization . Para aplicativos não gerenciados que hospedam o Common Language Runtime, especifique o sinalizador apropriado ao chamar o método CorBindToRuntimeEx Function.

Existem três opções para carregar assemblies de domínio neutro:

  • LoaderOptimization.SingleDomain não carrega assemblies como neutros em relação ao domínio, exceto Mscorlib, que é sempre carregado como neutro em relação ao domínio. Essa configuração é chamada de domínio único porque geralmente é usada quando o host está executando apenas um único aplicativo no processo.

  • LoaderOptimization.MultiDomain carrega todos os assemblies como neutros em relação ao domínio. Use essa configuração quando houver vários domínios de aplicativo no processo, todos os quais executam o mesmo código.

  • LoaderOptimization.MultiDomainHost carrega assemblies de nome forte como neutros em relação ao domínio, se eles e todas as suas dependências foram instalados na cache de assembly global. Outros assemblies são carregados e compilados por JIT separadamente para cada domínio de aplicativo em que são carregados e, assim, podem ser descarregados do processo. Use essa configuração ao executar mais de um aplicativo no mesmo processo ou se você tiver uma mistura de conjuntos compartilhados por muitos domínios de aplicação e conjuntos que precisam ser descarregados do processo.

Códigos compilados por JIT não podem ser compartilhados por assemblies carregados no contexto de carga, usando o método LoadFrom da classe Assembly, ou carregados com base em imagens usando sobrecargas do método Load que especificam matrizes de bytes.

Os assemblies que foram compilados para código nativo usando o Ngen.exe (Gerador de Imagem Nativa) poderão ser compartilhados entre domínios de aplicativo, se eles tiverem sido carregados como neutros em relação ao domínio na primeira vez em que eles forem carregados em um processo.

O código compilado por JIT para o assembly que contém o ponto de entrada do aplicativo será compartilhado somente se todas as suas dependências puderem ser compartilhadas.

Um assembly neutro em relação ao domínio pode ser compilado por JIT mais de uma vez. Por exemplo, quando os conjuntos de concessões de segurança de dois domínios de aplicativo são diferentes, eles não podem compartilhar o mesmo código compilado por JIT. No entanto, cada cópia do assembly compilado por JIT pode ser compartilhada com outros domínios de aplicativo que tenham o mesmo conjunto de concessões.

Ao decidir se deve carregar assemblies como neutros em relação ao domínio, você deve fazer uma escolha entre reduzir o uso de memória e outros fatores de desempenho.

  • O acesso a dados e métodos estáticos é mais lento para assemblies neutros em relação ao domínio devido à necessidade de isolamento dos assemblies. Cada domínio de aplicativo que acessa o assembly deve ter uma cópia separada dos dados estáticos, para impedir que referências a objetos em campos estáticos ultrapassem os limites de domínio. Como resultado, o runtime contém lógica adicional para direcionar um chamador para a cópia apropriada dos dados estáticos ou do método. Essa lógica extra reduz a velocidade da chamada.

  • Todas as dependências de um assembly devem ser localizadas e carregadas quando o assembly é carregado de forma neutra em termos de domínio, pois uma dependência que não pode ser carregada de forma neutra em termos de domínio impede que o assembly seja carregado dessa forma.

Domínios do aplicativo e threads

Um domínio de aplicativo forma um limite de isolamento para segurança, controle de versão, confiabilidade e descarregamento de código gerenciado. Um thread é o constructo do sistema operacional usado pelo common language runtime para executar o código. Em tempo de execução, todo o código gerenciado é carregado em um domínio de aplicativo e é executado por um ou mais threads gerenciados.

Não há uma correlação um-para-um entre domínios de aplicativo e threads. Vários threads podem ser executados em um único domínio de aplicativo a qualquer momento e um thread específico não está confinado a um único domínio de aplicativo. Ou seja, os threads são gratuitos para cruzar os limites de domínio do aplicativo; um novo thread não é criado para cada domínio do aplicativo.

A qualquer momento, cada thread é executado em um domínio de aplicativo. Zero, um ou vários threads podem estar sendo executados em qualquer domínio de aplicativo específico. O runtime controla quais threads estão em execução em quais domínios de aplicativo. Você pode localizar o domínio no qual um thread está sendo executado a qualquer momento chamando o Thread.GetDomain método.

Domínios e culturas de aplicativo

A cultura, que é representada por um CultureInfo objeto, está associada a threads. Você pode obter a cultura associada ao thread em execução no momento usando a CultureInfo.CurrentCulture propriedade e obter ou definir a cultura associada ao thread em execução no momento usando a Thread.CurrentCulture propriedade. Se a cultura associada a um thread tiver sido definida explicitamente usando a Thread.CurrentCulture propriedade, ela continuará associada a esse thread quando o thread ultrapassar os limites de domínio do aplicativo. Caso contrário, a cultura associada ao thread a qualquer momento é determinada pelo valor da CultureInfo.DefaultThreadCurrentCulture propriedade no domínio do aplicativo no qual o thread está sendo executado:

  • Se o valor da propriedade não for null, a cultura retornada pela propriedade estará associada ao thread (e, assim, retornada pelas propriedades Thread.CurrentCulture e CultureInfo.CurrentCulture).

  • Se o valor da propriedade for null, a cultura do sistema atual estará associada ao thread.

Programação com domínios de aplicativo

Os domínios de aplicativo geralmente são criados e manipulados programaticamente por hosts de runtime. No entanto, às vezes, um programa de aplicativo também pode querer trabalhar com domínios de aplicativo. Por exemplo, um programa de aplicativo pode carregar um componente de aplicativo em um domínio para poder descarregar o domínio (e o componente) sem precisar interromper todo o aplicativo.

A AppDomain é a interface programática para domínios de aplicação. Essa classe inclui métodos para criar e descarregar domínios, criar instâncias de tipos em domínios e registrar-se para várias notificações, como descarregamento de domínio do aplicativo. A tabela a seguir lista métodos comumente usados AppDomain .

Método AppDomain Descrição
CreateDomain Cria um novo domínio de aplicativo. É recomendável usar uma sobrecarga desse método que especifique um objeto AppDomainSetup. Essa é a maneira preferencial de definir as propriedades de um novo domínio, como a base de aplicativos ou o diretório raiz do aplicativo; o local do arquivo de configuração para o domínio; e o caminho de pesquisa que o common language runtime deve usar para carregar assemblies no domínio.
ExecuteAssembly e ExecuteAssemblyByName Executa um assembly no domínio do aplicativo. Esse é um método de instância, portanto, ele pode ser usado para executar código em outro domínio de aplicativo ao qual você tem uma referência.
CreateInstanceAndUnwrap Cria uma instância de um tipo especificado no domínio do aplicativo e retorna um proxy. Use esse método para evitar carregar o assembly contendo o tipo criado no assembly da chamada.
Unload Executa um desligamento elegante do domínio. O domínio do aplicativo não é descarregado até que todos os threads em execução no domínio tenham sido interrompidos ou não estejam mais no domínio.

Observação

O common language runtime não dá suporte à serialização de métodos globais, portanto, os delegados não podem ser usados para executar métodos globais em outros domínios de aplicativo.

As interfaces não gerenciadas descritas na Especificação de Interfaces de Hospedagem do Common Language Runtime também fornecem acesso aos domínios do aplicativo. Os hosts de runtime podem usar interfaces de código não gerenciado para criar e obter acesso aos domínios do aplicativo em um processo.

A variável de ambiente COMPLUS_LoaderOptimization

Uma variável de ambiente que define a política de otimização do carregador padrão de um aplicativo executável.

Sintaxe

COMPLUS_LoaderOptimization = 1

Observações

Um aplicativo típico carrega vários assemblies em um domínio de aplicativo antes que o código que eles contêm possa ser executado.

A maneira que o assembly é carregado determina se o seu código compilado por JIT (just-in-time) pode ser compartilhado por vários domínios de aplicativo no processo.

  • Se um assembly for carregado em domínio neutro, todos os domínios de aplicativo que compartilhem o mesmo conjunto de concessão de segurança poderão compartilhar o mesmo código compilado por JIT. Isso reduz a memória exigida pelo aplicativo.

  • Se um assembly não for carregado em domínio neutro, ele deverá ser compilado por JIT em cada domínio de aplicativo em que for carregado e o carregador não deverá compartilhar recursos internos entre domínios de aplicativo.

Quando definido como 1, o sinalizador de ambiente COMPLUS_LoaderOptimization força o host de runtime a carregar todos os assemblies de maneira de domínio não neutro, conhecido como SingleDomain. SingleDomain não carrega assemblies como neutros em relação ao domínio, exceto Mscorlib, que é sempre carregado como neutro em relação ao domínio. Essa configuração é chamada de domínio único porque geralmente é usada quando o host está executando apenas um único aplicativo no processo.

Cuidado

O sinalizador de ambiente COMPLUS_LoaderOptimization foi projetado para ser usado em cenários de diagnóstico e teste. Ter o sinalizador ativado pode causar uma lentidão severa e aumentar o uso de memória.

Exemplo de código

Para forçar todos os assemblies a não serem carregados como domínios neutros para o serviço IISADMIN, pode ser obtido acrescentando COMPLUS_LoaderOptimization=1 ao valor de várias cadeia de caracteres de ambiente na chave HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\IISADMIN.

Key = HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\IISADMIN
Name = Environment
Type = REG_MULTI_SZ
Value (to append) = COMPLUS_LoaderOptimization=1

Consulte também