Domínios de aplicação
Nota
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.
Sistemas operacionais e ambientes de tempo de execução 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, além de descarregar assemblies. Os domínios de aplicativo são normalmente criados por hosts de tempo de execução, que são responsáveis por inicializar o Common Language Runtime antes que um aplicativo seja executado.
Os benefícios de isolar aplicações
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, não é possível fazer chamadas diretas entre dois processos. Em vez disso, você deve usar proxies, que fornecem um nível de indireção.
O código gerenciado deve passar 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 no qual ele está sendo executado não funcione corretamente. Diz-se que o código que passa no teste de verificação é seguro para tipos. A capacidade de verificar o código como type-safe permite que o Common Language Runtime forneça um nível de isolamento tão grande 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.
O isolamento de 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 tal forma que os controles não possam acessar os dados e recursos uns dos outros.
O isolamento fornecido pelos domínios de 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.
As aplicações individuais podem ser interrompidas sem parar todo o processo. O uso de domínios de aplicativo permite descarregar o código em execução em um único aplicativo.
Nota
Não é possível descarregar montagens ou tipos individuais. Apenas 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 duas máquinas. Como tal, os metadados do objeto que está sendo referenciado devem estar disponíveis para ambos os domínios de aplicativo para permitir que a chamada de método seja compilada corretamente em JIT. Se o domínio chamador 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, veja System.MarshalByRefObject.
O comportamento do código é definido pelo aplicativo no qual ele é executado. Em outras palavras, o domínio do aplicativo fornece definições de configuração, como diretivas de versão do aplicativo, o local de quaisquer assemblies remotos que ele acessa e informações sobre onde localizar assemblies que são 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á sendo executado.
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. A execução de um aplicativo típico faz com que vários assemblies sejam 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 com neutralidade de domínio, todos os domínios de aplicativo que compartilham o mesmo conjunto de concessão 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 conjunto nunca pode ser descarregado do processo.
Se um assembly não for carregado com neutralidade de domínio, ele deverá ser compilado em JIT em cada domínio de aplicativo no qual é carregado. No entanto, o assembly pode ser descarregado do processo descarregando todos os domínios de aplicativo nos quais ele é carregado.
O host de tempo de execução determina se os assemblies devem ser carregados como neutros em termos de domínio quando carregam o tempo de execução 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 .
Há três opções para carregar assemblies neutros de domínio:
LoaderOptimization.SingleDomain não carrega assemblies como domínio neutro, exceto Mscorlib, que é sempre carregado domínio neutro. Essa configuração é chamada de domínio único porque é comumente usada quando o host está executando apenas um único aplicativo no processo.
LoaderOptimization.MultiDomain Carrega todos os assemblies como neutros em termos de domínio. Use essa configuração quando houver vários domínios de aplicativo no processo, todos executando o mesmo código.
LoaderOptimization.MultiDomainHost Carrega assemblies de nome forte como domínio neutro, se eles e todas as suas dependências tiverem sido instalados no cache de assembly global. Outros assemblies são carregados e compilados JIT separadamente para cada domínio de aplicativo no qual são carregados e, portanto, 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 assemblies compartilhados por muitos domínios de aplicativo e assemblies que precisam ser descarregados do processo.
O código compilado por JIT não pode ser compartilhado para assemblies carregados no contexto load-from, usando o LoadFrom método da Assembly classe, ou carregado de imagens usando sobrecargas do método que especificam matrizes de Load bytes.
Os assemblies que foram compilados para código nativo usando o Ngen.exe (Native Image Generator) podem ser compartilhados entre domínios de aplicativo, se forem carregados com neutralidade de domínio na primeira vez que forem carregados em um processo.
O código compilado por JIT para o assembly que contém o ponto de entrada do aplicativo é compartilhado somente se todas as suas dependências puderem ser compartilhadas.
Um assembly neutro de domínio pode ser compilado JIT mais de uma vez. Por exemplo, quando os conjuntos de concessão 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ão.
Ao decidir se deseja carregar assemblies como neutros em termos de domínio, você deve fazer uma compensação entre reduzir o uso de memória e outros fatores de desempenho.
O acesso a dados estáticos e métodos é mais lento para assemblies neutros em termos de domínio devido à necessidade de isolar assemblies. Cada domínio de aplicativo que acessa o assembly deve ter uma cópia separada dos dados estáticos, para evitar que referências a objetos em campos estáticos cruzem os limites do domínio. Como resultado, o tempo de execução contém lógica adicional para direcionar um chamador para a cópia apropriada dos dados estáticos ou método. Esta lógica extra torna a chamada mais lenta.
Todas as dependências de um assembly devem ser localizadas e carregadas quando o assembly é carregado com domínio neutro, porque uma dependência que não pode ser carregada com domínio neutro impede que o assembly seja carregado com domínio neutro.
Domínios e threads de aplicativos
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 é a construção do sistema operacional usada pelo common language runtime para executar 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 livres para cruzar os limites do domínio do aplicativo; Um novo thread não é criado para cada domínio de 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. O tempo de execução controla quais threads estão sendo executados 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 aplicação
A cultura, que é representada por um CultureInfo objeto, está associada a fios. Você pode obter a cultura associada ao thread em execução no momento usando a CultureInfo.CurrentCulture propriedade e pode 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á a ser associada a esse thread quando o thread cruzar os limites do domínio do aplicativo. Caso contrário, a cultura associada ao thread a qualquer momento é determinada pelo valor da propriedade no domínio do CultureInfo.DefaultThreadCurrentCulture aplicativo no qual o thread está sendo executado:
Se o valor da propriedade não
null
for , a cultura que é retornada pela propriedade está associada ao thread (e, portanto, retornada Thread.CurrentCulture pelas propriedades e CultureInfo.CurrentCulture ).Se o valor da propriedade for
null
, a cultura do sistema atual está associada ao thread.
Programação com domínios de aplicação
Os domínios de aplicativo geralmente são criados e manipulados programaticamente por hosts de tempo de execução. 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 ter que parar o aplicativo inteiro.
O 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 para várias notificações, como descarregamento de domínio de aplicativo. A tabela a seguir lista os métodos mais usados AppDomain .
Método AppDomain | Description |
---|---|
CreateDomain | Cria um novo domínio de aplicativo. É recomendável que você use uma sobrecarga desse método que especifica um AppDomainSetup objeto. Essa é a maneira preferida de definir as propriedades de um novo domínio, como a base do aplicativo 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. Este é um método de instância, portanto, 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 que contém o tipo criado no assembly chamador. |
Unload | Executa um desligamento normal do domínio. O domínio do aplicativo não é descarregado até que todos os threads em execução no domínio tenham parado ou não estejam mais no domínio. |
Nota
O common language runtime não oferece 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 Common Language Runtime Hosting Interfaces Specification também fornecem acesso a domínios de aplicativo. Os hosts de tempo de execução podem usar interfaces de código não gerenciado para criar e obter acesso aos domínios de aplicativo dentro de 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 como o assembly é carregado determina se seu código compilado just-in-time (JIT) pode ser compartilhado por vários domínios de aplicativo no processo.
Se um assembly for carregado com neutralidade de domínio, todos os domínios de aplicativo que compartilham 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 com neutralidade de domínio, ele deverá ser compilado em JIT em cada domínio de aplicativo no qual é 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 tempo de execução a carregar todos os assemblies de forma não neutra em termos de domínio, conhecida como SingleDomain. SingleDomain não carrega assemblies como domínio neutro, exceto Mscorlib, que é sempre carregado domínio neutro. Essa configuração é chamada de domínio único porque é comumente usada quando o host está executando apenas um único aplicativo no processo.
Atenção
O sinalizador de ambiente COMPLUS_LoaderOptimization foi projetado para ser usado em cenários de diagnóstico e teste. Ter o sinalizador ligado pode causar lentidão severa e aumento no uso de memória.
Exemplo de código
Para forçar todos os assemblies a não serem carregados como neutros de domínio para o serviço IISADMIN pode ser obtido anexando COMPLUS_LoaderOptimization=1
ao valor Multi-String do 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