Partilhar via


Práticas recomendadas de confiabilidade

As seguintes regras de confiabilidade são orientadas ao SQL Server; no entanto, eles também se aplicam a qualquer aplicativo de servidor baseado em host. É extremamente importante que servidores como o SQL Server não vazem recursos e não sejam derrubados. No entanto, isso não pode ser feito escrevendo código de back-out para cada método que altera o estado de um objeto. O objetivo não é escrever código gerenciado 100% confiável que se recupere de quaisquer erros em todos os locais com código de back-out. Seria uma tarefa difícil e com poucas hipóteses de êxito. O Common Language Runtime (CLR) não pode facilmente fornecer garantias fortes o suficiente para o código gerenciado para tornar viável a escrita de código perfeito. Observe que, ao contrário do ASP.NET, o SQL Server usa apenas um processo que não pode ser reciclado sem desativar um banco de dados por um tempo inaceitavelmente longo.

Com essas garantias mais fracas e sendo executadas em um único processo, a confiabilidade é baseada no encerramento de threads ou na reciclagem de domínios de aplicativos quando necessário e na tomada de precauções para garantir que os recursos do sistema operacional, como identificadores ou memória, não sejam vazados. Mesmo com essa restrição de confiabilidade mais simples, ainda há um requisito de confiabilidade significativo:

  • Nunca vaze recursos do sistema operacional.

  • Identifique todos os bloqueios gerenciados em todos os formulários para o CLR.

  • Nunca quebre o estado compartilhado do domínio entre aplicativos, permitindo que AppDomain a reciclagem funcione sem problemas.

Embora seja teoricamente possível escrever código gerenciado para lidar com ThreadAbortException, StackOverflowExceptione OutOfMemoryException exceções, esperar que os desenvolvedores escrevam um código tão robusto em todo um aplicativo não é razoável. Por esse motivo, exceções fora de banda resultam no encerramento do thread de execução; e se o thread terminado estava editando o estado compartilhado, que pode ser determinado se o thread mantém um bloqueio, então o AppDomain é descarregado. Quando um método que está editando o estado compartilhado é encerrado, o estado será corrompido porque não é possível escrever código de back-out confiável para atualizações para o estado compartilhado.

No .NET Framework versão 2.0, o único host que requer confiabilidade é o SQL Server. Se o assembly for executado no SQL Server, você deverá fazer o trabalho de confiabilidade para cada parte desse assembly, mesmo que haja recursos específicos desabilitados durante a execução no banco de dados. Isso é necessário porque o mecanismo de análise de código examina o código no nível de montagem e não pode diferenciar o código desabilitado. Outra consideração de programação do SQL Server é que o SQL Server executa tudo em um processo e AppDomain a reciclagem é usada para limpar todos os recursos, como memória e identificadores do sistema operacional.

Você não pode depender de finalizadores ou destruidores ou try/finally blocos para código de back-out. Podem ser interrompidos ou não chamados.

Exceções assíncronas podem ser lançadas em locais inesperados, possivelmente em todas as instruções da máquina: ThreadAbortException, StackOverflowExceptione OutOfMemoryException.

Threads gerenciados não são necessariamente threads Win32 em SQL; podem ser fibras.

O estado compartilhado mutável em todo o processo ou entre aplicativos é extremamente difícil de alterar com segurança e deve ser evitado sempre que possível.

Condições de falta de memória não são raras no SQL Server.

Se as bibliotecas hospedadas no SQL Server não atualizarem corretamente seu estado compartilhado, há uma alta probabilidade de que o código não será recuperado até que o banco de dados tenha sido reiniciado. Além disso, em alguns casos extremos, é possível que isso possa fazer com que o processo do SQL Server falhe, fazendo com que o banco de dados seja reinicializado. Reinicializar o banco de dados pode derrubar um site ou afetar as operações da empresa, prejudicando a disponibilidade. Um vazamento lento de recursos do sistema operacional, como memória ou identificadores, pode fazer com que o servidor eventualmente falhe na alocação de identificadores sem possibilidade de recuperação, ou potencialmente o servidor pode degradar lentamente o desempenho e reduzir a disponibilidade do aplicativo do cliente. É evidente que queremos evitar estes cenários.

Regras de boas práticas

A introdução concentrou-se no que a revisão de código para o código gerenciado que é executado no servidor teria que capturar para aumentar a estabilidade e a confiabilidade da estrutura. Todas essas verificações são boas práticas em geral e uma necessidade absoluta no servidor.

Em face de um bloqueio inativo ou restrição de recursos, o SQL Server anulará um thread ou destruirá um AppDomainarquivo . Quando isso acontece, apenas o código de back-out em uma região de execução restrita (CER) tem a garantia de ser executado.

Use o SafeHandle para evitar vazamentos de recursos

No caso de uma AppDomain descarga, você não pode depender de finally blocos ou finalizadores sendo executados, por isso é importante abstrair todo o acesso a recursos do sistema operacional através da SafeHandle classe em vez de IntPtr, HandleRefou classes semelhantes. Isso permite que o CLR rastreie e feche as alças que você usa, mesmo no caso de AppDomain desmontagem. SafeHandle estará usando um finalizador crítico que o CLR sempre executará.

A alça do sistema operacional é armazenada na alça segura desde o momento em que é criada até o momento em que é liberada. Não há nenhuma janela durante a qual possa ocorrer um ThreadAbortException vazamento de uma alça. Além disso, a chamada de plataforma fará referência à contagem da alça, o que permite o rastreamento próximo da vida útil da alça, evitando um problema de segurança com uma condição de corrida entre Dispose e um método que está usando a alça no momento.

A maioria das classes que atualmente têm um finalizador para simplesmente limpar um identificador do sistema operacional não precisará mais do finalizador. Em vez disso, o finalizador estará na SafeHandle classe derivada.

Note que SafeHandle não é um substituto para IDisposable.Dispose. Ainda há potencial contenção de recursos e vantagens de desempenho para descartar explicitamente os recursos do sistema operacional. Basta perceber que finally os blocos que descartam explicitamente os recursos podem não ser executados até a conclusão.

SafeHandle Permite que você implemente seu próprio ReleaseHandle método que executa o trabalho para liberar o identificador, como passar o estado para um identificador do sistema operacional liberando rotina ou liberando um conjunto de alças em um loop. O CLR garante que esse método é executado. É responsabilidade do autor da implementação garantir que a alça seja liberada ReleaseHandle em todas as circunstâncias. Se isso não for feito, a alça será vazada, o que muitas vezes resulta no vazamento de recursos nativos associados à alça. Portanto, é fundamental estruturar SafeHandle classes derivadas de modo que a ReleaseHandle implementação não exija a alocação de quaisquer recursos que possam não estar disponíveis no momento da invocação. Observe que é permitido chamar métodos que podem falhar dentro da implementação de ReleaseHandle , desde que seu código possa lidar com essas falhas e concluir o contrato para liberar o identificador nativo. Para fins de depuração, ReleaseHandle tem um Boolean valor de retorno que pode ser definido como false se for encontrado um erro catastrófico que impeça a liberação do recurso. Isso ativará o MDA releaseHandleFailed , se habilitado, para ajudar na identificação do problema. Não afeta o tempo de execução de nenhuma outra forma; ReleaseHandle não será chamado novamente para o mesmo recurso e, consequentemente, a alça será vazada.

SafeHandle não é adequada em determinados contextos. Como o ReleaseHandle método pode ser executado em um GC thread de finalizador, quaisquer alças que precisam ser liberadas em um thread específico não devem ser encapsuladas em um SafeHandlearquivo .

Os wrappers chamáveis em tempo de execução (RCWs) podem ser limpos pelo CLR sem código adicional. Para o código que usa invocar plataforma e trata um objeto COM como um IUnknown* ou um IntPtr, o código deve ser reescrito para usar um RCW. SafeHandle pode não ser adequado para esse cenário devido à possibilidade de um método de liberação não gerenciado chamar de volta para o código gerenciado.

Regra de análise de código

Use SafeHandle para encapsular recursos do sistema operacional. Não utilize HandleRef ou campos do tipo IntPtr.

Certifique-se de que os finalizadores não precisam ser executados para evitar o vazamento de recursos do sistema operacional

Analise seus finalizadores cuidadosamente para garantir que, mesmo que eles não sejam executados, um recurso crítico do sistema operacional não seja vazado. Ao contrário de uma descarga normal AppDomain quando o aplicativo está sendo executado em um estado estável ou quando um servidor, como o SQL Server, é desligado, os objetos não são finalizados durante uma descarga abrupta AppDomain . Certifique-se de que os recursos não são vazados no caso de uma descarga abrupta, uma vez que a correção de um aplicativo não pode ser garantida, mas a integridade do servidor deve ser mantida não vazando recursos. Use SafeHandle para liberar quaisquer recursos do sistema operacional.

Certifique-se de que, finalmente, as cláusulas não precisam ser executadas para evitar o vazamento de recursos do sistema operacional

finally não é garantido que as cláusulas sejam executadas fora dos CERs, exigindo que os desenvolvedores de bibliotecas não dependam do código dentro de um finally bloco para liberar recursos não gerenciados. Usar SafeHandle é a solução recomendada.

Regra de análise de código

Use SafeHandle para limpar recursos do sistema operacional em vez de Finalize. Não use IntPtrpara SafeHandle encapsular recursos. Se a cláusula final tiver de ser executada, coloque-a num CER.

Todos os bloqueios devem passar pelo código de bloqueio gerenciado existente

O CLR deve saber quando o código está em um bloqueio para que ele saiba derrubar o AppDomain em vez de apenas abortar o thread. Abortar o thread pode ser perigoso, pois os dados operados pelo thread podem ser deixados em um estado inconsistente. Portanto, o todo AppDomain tem que ser reciclado. As consequências de não identificar um bloqueio podem ser impasses ou resultados incorretos. Use os métodos BeginCriticalRegion e EndCriticalRegion para identificar regiões de bloqueio. Eles são métodos estáticos na Thread classe que se aplicam apenas ao thread atual, ajudando a impedir que um thread edite a contagem de bloqueios de outro thread.

Enter e Exit ter essa notificação CLR incorporada, portanto, seu uso é recomendado, bem como o uso da instrução lock, que usa esses métodos.

Outros mecanismos de bloqueio, como bloqueios de rotação, devem AutoResetEvent chamar esses métodos para notificar o CLR de que uma seção crítica está sendo inserida. Estes métodos não levam nenhum bloqueio; eles informam ao CLR que o código está sendo executado em uma seção crítica e abortar o thread pode deixar o estado compartilhado inconsistente. Se você definiu seu próprio tipo de bloqueio, como uma classe personalizada ReaderWriterLock , use esses métodos de contagem de bloqueio.

Regra de análise de código

Marque e identifique todas as fechaduras usando BeginCriticalRegion e EndCriticalRegion. Não utilize CompareExchange, Increment, e Decrement num loop. Não faça uma plataforma invocar as variantes Win32 desses métodos. Não utilizar Sleep em loop. Não utilize campos voláteis.

Código de limpeza deve estar em um último ou um bloco de captura, Não seguindo uma captura

O código de limpeza nunca deve seguir um catch bloco, deve estar em um finally ou no catch próprio bloco. Esta deve ser uma boa prática normal. Um finally bloco é geralmente preferido porque executa o mesmo código quando uma exceção é lançada e quando o final do try bloco é normalmente encontrado. No caso de uma exceção inesperada ser lançada, por exemplo, um ThreadAbortException, o código de limpeza não será executado. Quaisquer recursos não gerenciados que você limparia em um finally deve idealmente ser embrulhado em um SafeHandle para evitar vazamentos. Observe que a palavra-chave C# using pode ser usada efetivamente para descartar objetos, incluindo identificadores.

Embora AppDomain a reciclagem possa limpar recursos no segmento do finalizador, ainda é importante colocar o código de limpeza no lugar correto. Observe que, se um thread receber uma exceção assíncrona sem segurar um bloqueio, o CLR tentará encerrar o próprio thread sem ter que reciclar o AppDomain. Garantir que os recursos são limpos mais cedo do que tarde ajuda, disponibilizando mais recursos e gerindo melhor o tempo de vida. Se você não fechar explicitamente um identificador para um arquivo em algum caminho de código de erro, aguarde até que o SafeHandle finalizador o limpe, da próxima vez que o código for executado, ele poderá falhar ao tentar acessar exatamente o mesmo arquivo se o finalizador ainda não tiver sido executado. Por esse motivo, garantir que o código de limpeza exista e funcione corretamente ajudará a recuperar de falhas de forma mais limpa e rápida, mesmo que não seja estritamente necessário.

Regra de análise de código

O código de limpeza depois catch precisa estar em um finally bloco. Coloque chamadas para descartar em um bloco final. catch Os bloqueios devem terminar em um lance ou relançamento. Embora haja exceções, como o código que deteta se uma conexão de rede pode ser estabelecida onde você pode obter qualquer um de um grande número de exceções, qualquer código que exija a captura de um número de exceções em circunstâncias normais deve dar uma indicação de que o código deve ser testado para ver se será bem-sucedido.

O estado compartilhado mutável em todo o processo entre domínios de aplicativo deve ser eliminado ou usar uma região de execução restrita

Conforme descrito na introdução, pode ser muito difícil escrever código gerenciado que monitore o estado compartilhado em todo o processo entre domínios de aplicativo de maneira confiável. O estado compartilhado em todo o processo é qualquer tipo de estrutura de dados compartilhada entre domínios de aplicativo, seja no código Win32, dentro do CLR ou no código gerenciado usando comunicação remota. Qualquer estado compartilhado mutável é muito difícil de escrever corretamente em código gerenciado, e qualquer estado compartilhado estático pode ser feito apenas com muito cuidado. Se você tiver um estado compartilhado em todo o processo ou em toda a máquina, encontre alguma maneira de eliminá-lo ou proteger o estado compartilhado usando uma região de execução restrita (CER). Observe que qualquer biblioteca com estado compartilhado que não seja identificada e corrigida pode fazer com que um host, como o SQL Server, que exija descarregamento limpo AppDomain falhe.

Se o código usa um objeto COM, evite compartilhar esse objeto COM entre domínios de aplicativo.

Os bloqueios não funcionam em todo o processo ou entre domínios de aplicativo.

No passado, Enter e a instrução de bloqueio foram usados para criar bloqueios de processo globais. Por exemplo, isso ocorre ao AppDomain bloquear classes ágeis, como Type instâncias de assemblies não compartilhados, Thread objetos, cadeias de caracteres internadas e algumas cadeias de caracteres compartilhadas entre domínios de aplicativo usando comunicação remota. Essas fechaduras não são mais amplas em todo o processo. Para identificar a presença de um bloqueio de domínio entre aplicativos em todo o processo, determine se o código dentro do bloqueio usa algum recurso externo persistente, como um arquivo no disco ou possivelmente um banco de dados.

Observe que fazer um bloqueio dentro de um AppDomain pode causar problemas se o código protegido usar um recurso externo, porque esse código pode ser executado simultaneamente em vários domínios de aplicativo. Isso pode ser um problema ao gravar em um arquivo de log ou vincular a um soquete para todo o processo. Essas alterações significam que não há uma maneira fácil, usando código gerenciado, de obter um bloqueio global de processo, além de usar um nome Mutex ou Semaphore instância. Crie código que não seja executado em dois domínios de aplicativo simultaneamente ou use as Mutex classes or Semaphore . Se o código existente não puder ser alterado, não use um Win32 chamado mutex para obter essa sincronização, porque a execução no modo de fibra significa que você não pode garantir que o mesmo thread do sistema operacional irá adquirir e liberar um mutex. Você deve usar a classe gerenciadaMutex, ou um chamado ManualResetEvent, ou um Semaphore para AutoResetEventsincronizar o bloqueio de código de uma maneira que o CLR está ciente em vez de sincronizar o bloqueio usando código não gerenciado.

Evite lock(typeof(MyType))

Objetos privados e públicos Type em assemblies compartilhados com apenas uma cópia do código compartilhado em todos os domínios de aplicativo também apresentam problemas. Para assemblies compartilhados, há apenas uma instância de um Type por processo, o que significa que vários domínios de aplicativo compartilham exatamente a mesma Type instância. Fazer um bloqueio em uma Type instância requer um bloqueio que afeta todo o processo, não apenas o AppDomain. Se alguém AppDomain pegar um bloqueio em um Type objeto, então esse thread é abruptamente abortado, ele não vai liberar o bloqueio. Esse bloqueio, então, pode causar o bloqueio de outros domínios de aplicativo.

Uma boa maneira de fazer bloqueios em métodos estáticos envolve a adição de um objeto de sincronização interno estático ao código. Isso pode ser inicializado no construtor de classe se um estiver presente, mas se não ele pode ser inicializado assim:

private static Object s_InternalSyncObject;
private static Object InternalSyncObject
{
    get
    {
        if (s_InternalSyncObject == null)
        {
            Object o = new Object();
            Interlocked.CompareExchange(
                ref s_InternalSyncObject, o, null);
        }
        return s_InternalSyncObject;
    }
}

Em seguida, ao pegar um bloqueio, use a InternalSyncObject propriedade para obter um objeto para bloquear. Você não precisará usar a propriedade se tiver inicializado o objeto de sincronização interno em seu construtor de classe. O código de inicialização do bloqueio de verificação dupla deve ser semelhante a este exemplo:

public static MyClass SingletonProperty
{
    get
    {
        if (s_SingletonProperty == null)
        {
            lock(InternalSyncObject)
            {
                // Do not use lock(typeof(MyClass))
                if (s_SingletonProperty == null)
                {
                    MyClass tmp = new MyClass(…);
                    // Do all initialization before publishing
                    s_SingletonProperty = tmp;
                }
            }
        }
        return s_SingletonProperty;
    }
}

Uma nota sobre lock(this)

É geralmente aceitável bloquear um objeto individual que seja acessível publicamente. No entanto, se o objeto for um objeto singleton que pode causar o bloqueio de um subsistema inteiro, considere usar o padrão de design acima também. Por exemplo, um bloqueio em um SecurityManager objeto pode causar um impasse dentro do AppDomain tornando todo AppDomain o inutilizável. É uma boa prática não bloquear um objeto deste tipo acessível ao público. No entanto, um bloqueio em uma coleção ou matriz individual geralmente não deve apresentar um problema.

Regra de análise de código

Não bloqueie tipos que possam ser usados em domínios de aplicativos ou que não tenham um forte senso de identidade. Não chame um , , , PropertyInfoString, ValueType, Thread, ou qualquer objeto que derive de MarshalByRefObject. MethodInfoTypeEnter

Remover GC. Chamadas KeepAlive

Uma quantidade significativa de código existente não usa KeepAlive quando deveria ou usa-o quando não é apropriado. Após a conversão para SafeHandle, as classes não precisam chamar KeepAlive, supondo que não tenham um finalizador, mas dependam para SafeHandle finalizar os identificadores do sistema operacional. Embora o custo de desempenho de reter uma chamada para KeepAlive possa ser insignificante, a perceção de que uma chamada para KeepAlive é necessária ou suficiente para resolver um problema vitalício que pode não existir mais torna o código mais difícil de manter. No entanto, ao usar os wrappers chamáveis CLR (RCWs) de interoperabilidade COM, KeepAlive ainda é exigido pelo código.

Regra de análise de código

Remover KeepAlive.

Usar o atributo HostProtection

O HostProtectionAttribute (HPA) fornece o uso de ações de segurança declarativas para determinar os requisitos de proteção do host, permitindo que o host impeça que até mesmo código totalmente confiável chame certos métodos que são inadequados para determinado host, como Exit ou Show para o SQL Server.

O HPA afeta apenas aplicativos não gerenciados que hospedam o Common Language Runtime e implementam proteção de host, como o SQL Server. Quando aplicada, a ação de segurança resulta na criação de uma demanda de link com base nos recursos do host que a classe ou método expõe. Se o código for executado em um aplicativo cliente ou em um servidor que não esteja protegido por host, o atributo "evapora"; não é detetado e, portanto, não é aplicado.

Importante

O objetivo desse atributo é impor diretrizes de modelo de programação específicas do host, não o comportamento de segurança. Embora uma demanda de link seja usada para verificar a conformidade com os requisitos do modelo de programação, a HostProtectionAttribute não é uma permissão de segurança.

Se o host não tiver requisitos de modelo de programação, as demandas de link não ocorrerão.

Este atributo identifica o seguinte:

  • Métodos ou classes que não se ajustam ao modelo de programação do host, mas são benignos.

  • Métodos ou classes que não se encaixam no modelo de programação do host e podem levar à desestabilização do código do usuário gerenciado pelo servidor.

  • Métodos ou classes que não se encaixam no modelo de programação do host e podem levar a uma desestabilização do próprio processo do servidor.

Nota

Se você estiver criando uma biblioteca de classes que deve ser chamada por aplicativos que podem ser executados em um ambiente protegido de host, você deve aplicar esse atributo aos membros que expõem HostProtectionResource categorias de recursos. Os membros da biblioteca de classes do .NET Framework com esse atributo fazem com que apenas o chamador imediato seja verificado. O membro da biblioteca também deve fazer uma verificação de seu chamador imediato da mesma maneira.

Por favor, encontre mais informações sobre a HPA em HostProtectionAttribute.

Regra de análise de código

Para o SQL Server, todos os métodos usados para introduzir sincronização ou threading devem ser identificados com o HPA. Isso inclui métodos que compartilham estado, são sincronizados ou gerenciam processos externos. Os valores que afetam o HostProtectionResource SQL Server são SharedState, Synchronizatione ExternalProcessMgmt. No entanto, qualquer método que exponha qualquer HostProtectionResource deve ser identificado por um HPA, não apenas aqueles que usam recursos que afetam o SQL.

Não bloqueie indefinidamente em código não gerenciado

Bloquear em código não gerenciado em vez de em código gerenciado pode causar um ataque de negação de serviço porque o CLR não é capaz de abortar o thread. Um thread bloqueado impede que o CLR descarregue o AppDomain, pelo menos sem fazer algumas operações extremamente inseguras. Bloquear usando uma primitiva de sincronização do Windows é um exemplo claro de algo que não podemos permitir. O bloqueio em uma chamada para ReadFile em um soquete deve ser evitado, se possível — idealmente, a API do Windows deve fornecer um mecanismo para que uma operação como essa atinja o tempo limite.

Qualquer método que chame para nativo deve idealmente usar uma chamada Win32 com um tempo limite razoável e finito. Se o usuário tiver permissão para especificar o tempo limite, o usuário não deverá ter permissão para especificar um tempo limite infinito sem alguma permissão de segurança específica. Como diretriz, se um método for bloqueado por mais de ~10 segundos, você precisará usar uma versão que ofereça suporte a tempos limite ou precisará de suporte CLR adicional.

Aqui estão alguns exemplos de APIs problemáticas. Pipes (anônimos e nomeados) podem ser criados com um tempo limite; No entanto, o código deve garantir que nunca ligue CreateNamedPipe nem WaitNamedPipe com NMPWAIT_WAIT_FOREVER. Além disso, pode haver um bloqueio inesperado, mesmo que um tempo limite seja especificado. Chamar WriteFile um pipe anônimo será bloqueado até que todos os bytes sejam gravados, ou seja, se o buffer tiver dados não lidos nele, a WriteFile chamada será bloqueada até que o leitor libere espaço no buffer do pipe. Os soquetes devem sempre usar alguma API que respeite um mecanismo de tempo limite.

Regra de análise de código

Bloquear sem um tempo limite em código não gerenciado é um ataque de negação de serviço. Não execute chamadas de invocação de plataforma para WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, MsgWaitForMultipleObjectse MsgWaitForMultipleObjectsEx. Não utilize NMPWAIT_WAIT_FOREVER.

Identificar quaisquer recursos dependentes de STA

Identifique qualquer código que use apartamentos de thread único (STAs) COM. Os STAs são desabilitados no processo do SQL Server. Os recursos que dependem do CoInitialize, como contadores de desempenho ou a área de transferência, devem ser desabilitados no SQL Server.

Garantir que os finalizadores estejam livres de problemas de sincronização

Vários threads de finalizador podem existir em versões futuras do .NET Framework, o que significa que os finalizadores para diferentes instâncias do mesmo tipo são executados simultaneamente. Eles não precisam ser completamente seguros contra roscas; O coletor de lixo garante que apenas um thread executará o finalizador para uma determinada instância de objeto. No entanto, os finalizadores devem ser codificados para evitar condições de corrida e impasses ao serem executados simultaneamente em várias instâncias de objeto diferentes. Ao usar qualquer estado externo, como gravar em um arquivo de log, em um finalizador, os problemas de threading devem ser tratados. Não confie na finalização para fornecer segurança de rosca. Não use armazenamento local de thread, gerenciado ou nativo, para armazenar o estado no thread do finalizador.

Regra de análise de código

Os finalizadores devem estar livres de problemas de sincronização. Não use um estado mutável estático em um finalizador.

Evite memória não gerenciada, se possível

A memória não gerenciada pode ser vazada, assim como um identificador do sistema operacional. Se possível, tente usar a memória na pilha usando stackalloc ou um objeto gerenciado fixo, como a instrução fixed ou um GCHandle usando um byte[]. O GC acaba por limpá-los. No entanto, se você precisar alocar memória não gerenciada, considere usar uma classe derivada de para encapsular a alocação de SafeHandle memória.

Note-se que há pelo menos um caso em SafeHandle que não é adequado. Para chamadas de método COM que alocam ou liberam memória, é comum que uma DLL aloque memória via CoTaskMemAlloc então outra DLL libera essa memória com CoTaskMemFree. Usar SafeHandle nesses lugares seria inadequado, pois tentará vincular o tempo de vida da memória não gerenciada ao tempo de vida do SafeHandle em vez de permitir que a outra DLL controle o tempo de vida da memória.

Rever todas as utilizações das capturas(Exceção)

Os blocos de captura que capturam todas as exceções em vez de uma exceção específica agora também capturarão as exceções assíncronas. Examine cada bloco catch(Exception), procurando nenhum código de liberação de recurso ou backout importante que possa ser ignorado, bem como um comportamento potencialmente incorreto dentro do próprio bloco catch para lidar com um ThreadAbortException, StackOverflowExceptionou OutOfMemoryException. Observe que é possível que esse código esteja registrando ou fazendo algumas suposições de que ele pode ver apenas certas exceções, ou que sempre que uma exceção acontece, ele falhou exatamente por um motivo específico. Estes pressupostos poderão ter de ser atualizados para incluir ThreadAbortException.

Considere alterar todos os locais que capturam todas as exceções para capturar um tipo específico de exceção que você espera que seja lançada, como métodos FormatException de formatação de cadeia de caracteres. Isso evita que o bloco catch seja executado em exceções inesperadas e ajudará a garantir que o código não oculte bugs ao capturar exceções inesperadas. Como regra geral, nunca manipule uma exceção no código da biblioteca (o código que exige que você detete uma exceção pode indicar uma falha de design no código que você está chamando). Em alguns casos, você pode querer capturar uma exceção e lançar um tipo de exceção diferente para fornecer mais dados. Use exceções aninhadas nesse caso, armazenando a causa real da falha na InnerException propriedade da nova exceção.

Regra de análise de código

Revise todos os blocos catch no código gerenciado que capturam todos os objetos ou capturam todas as exceções. Em C#, isso significa sinalizar ambos e catchcatch(Exception){}{}. Considere tornar o tipo de exceção muito específico ou revise o código para garantir que ele não aja de maneira incorreta se detetar um tipo de exceção inesperado.

Não assuma que um thread gerenciado é um thread Win32 – É uma fibra

Usar o armazenamento local de thread gerenciado funciona, mas você não pode usar o armazenamento local de thread não gerenciado ou assumir que o código será executado no thread do sistema operacional atual novamente. Não altere configurações como a localidade do thread. Não ligue InitializeCriticalSection ou CreateMutex invoque via plataforma porque eles exigem que o thread do sistema operacional que entra em um bloqueio também saia do bloqueio. Uma vez que este não será o caso ao usar fibras, Win32 seções críticas e mutexes não podem ser usados em SQL diretamente. Observe que a classe gerenciada Mutex não lida com essas preocupações de afinidade de thread.

Você pode usar com segurança a maior parte do estado em um objeto gerenciado Thread , incluindo o armazenamento local de thread gerenciado e a cultura atual da interface do usuário do thread. Você também pode usar o ThreadStaticAttribute, que torna o valor de uma variável estática existente acessível apenas pelo thread gerenciado atual (essa é outra maneira de fazer armazenamento local de fibra no CLR). Por motivos de modelo de programação, não é possível alterar a cultura atual de um thread ao executar em SQL.

Regra de análise de código

O SQL Server é executado no modo de fibra; Não use armazenamento local de thread. Evite que a plataforma invoque chamadas para TlsAlloc, TlsFree, TlsGetValuee TlsSetValue.

Permitir que o SQL Server manipule a representação

Como a representação opera no nível de thread e o SQL pode ser executado no modo de fibra, o código gerenciado não deve representar usuários e não deve chamar RevertToSelf.

Regra de análise de código

Deixe o SQL Server lidar com a representação. Não utilize RevertToSelf, ImpersonateAnonymousToken, , DdeImpersonateClient, ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClientImpersonateSelf, RpcRevertToSelfRpcImpersonateClientRpcRevertToSelfExou SetThreadToken.

Não chame Thread::Suspend

A capacidade de suspender um thread pode parecer uma operação simples, mas pode causar deadlocks. Se um thread que contém um bloqueio é suspenso por um segundo thread e, em seguida, o segundo thread tenta tomar o mesmo bloqueio, ocorre um deadlock. Suspend pode interferir na segurança, no carregamento de classes, na comunicação remota e na reflexão atualmente.

Regra de análise de código

Não ligue para Suspend. Em vez disso, considere usar uma primitiva de sincronização real, como a Semaphore ou ManualResetEvent .

Proteja operações críticas com regiões de execução restritas e contratos de confiabilidade

Ao executar uma operação complexa que atualiza um status compartilhado ou que precisa ser deterministicamente bem-sucedida ou totalmente falhada, certifique-se de que ela esteja protegida por uma região de execução restrita (CER). Isso garante que o código seja executado em todos os casos, até mesmo um thread abrupto abortado ou um descarregamento abrupto AppDomain .

Um CER é um bloco específico try/finally imediatamente precedido por uma chamada para PrepareConstrainedRegions.

Isso instrui o compilador just-in-time a preparar todo o código no bloco final antes de executar o try bloco. Isso garante que o código no bloco final é construído e será executado em todos os casos. Não é incomum em um CER ter um bloco vazio try . O uso de um CER protege contra interrupções de thread assíncronas e exceções de falta de memória. Consulte ExecuteCodeWithGuaranteedCleanup para obter uma forma de CER que adicionalmente lida com estouros de pilha para código extremamente profundo.

Consulte também