Поделиться через


Рекомендации по обеспечению надежности

Обновлен: Ноябрь 2007

Следующие правила надежности больше всего связаны с SQL Server; однако они также применяются и к любому размещенному серверному приложению. Очень важно, чтобы такие серверы, как SQL Server, не приводили к утечке ресурсов и не выключались. Однако этого невозможно достичь посредством создания кода отката для каждого метода, изменяющего состояние объекта. Создание на сто процентов надежного и управляемого кода, который мог бы восстановиться после любых ошибок в любом расположении с кодом отката. Это очень непростая задача с незначительной вероятностью успешного выполнения. В среде CLR невозможно без труда обеспечить написание идеального управляемого кода. Обратите внимание, что в отличие от ASP.NET в SQL Server применяется только один процесс, который невозможно использовать повторно без отключения базы данных на чрезмерно длительный срок.

При таких малых гарантиях и при выполнении в одном процессе надежность основана на прерывании потоков или повторном использовании доменов приложения в случаях необходимости, а также на выполнении профилактических действий по предотвращению утечки работающих системных ресурсов, таких как дескрипторы или оперативная память. Даже с таким более простым ограничением надежности существует более важное требование к надежности:

  • Никогда нельзя допускать утечку системных ресурсов.

  • Укажите в среде CRL все управляемые блокировки во всех формах.

  • Никогда не прерывайте общее состояние различных доменов приложения, обеспечивая нормальное функционирование при повторном использовании AppDomain.

Хотя теоретически возможно создать управляемый код для обработки исключений ThreadAbortException, StackOverflowException и OutOfMemoryException, нет смысла ожидать от разработчиков создания такого трудоемкого кода во всем приложении. По этой причине исключения, находящиеся за пределами диапазона, приводят к прерыванию выполняемого потока, а если прерванный поток изменял общее состояние, что может быть определено по тому, имеет ли поток блокировку, то объект AppDomain выгружается. При прерывании метода, изменяющего общее состояние, это состояние окажется поврежденным, так как невозможно написать надежный код отката для обновлений общего состояния.

В .NET Framework версии 2.0 единственным нуждающимся в обеспечении надежности основным приложением является SQL Server. Если сборка будет работать на сервере SQL Server, необходимо выполнить действия по обеспечению надежности для каждой части этой сборки, даже если присутствуют специальные функциональные возможности, которые являются отключенными при работе в базе данных. Это необходимо, так как модуль анализа кода рассматривает код на уровне сборки и не может опознать отключенный код. Другим вопросом, связанным с программированием в среде SQL Server, является выполнение всего в одном процессе на SQL Server, а также применение повторного использования AppDomain для очистки всех ресурсов, таких как память и дескрипторы операционной системы.

Код отката не должен зависеть от методов завершения, деструкторов или блоков try/finally. Они могут быть прерваны или вообще не вызваны.

Асинхронные исключения могут быть созданы в неожиданных расположениях, возможно при каждой инструкции машинного кода ThreadAbortException, StackOverflowException и OutOfMemoryException.

Управляемые потоки не обязательно должны являться потоками Win32 в SQL; они могут являться волокнами.

Изменяемое общее состояние, применимое ко всему процессу или к нескольким доменам, очень трудно изменить безопасным способом, поэтому этого следует по возможности избегать.

В SQL Server нередко возникает нехватка памяти.

Если библиотеки, размещенные в SQL Server, не обновляют свое общее состояние должным образом, очень велика вероятность того, что код не будет восстановлен до перезапуска базы данных. Кроме того, в некоторых крайних случаях это может привести к сбою процесса сервера SQL Server, что приведет к перезагрузке базы данных. Перезагрузка базы данных может привести к отключению веб-узла или повлиять на операции всей организации, что, в свою очередь, затронет доступность данных. Медленная утечка ресурсов операционной системы, таких как память или дескрипторы, может привести к тому, что сервер не сможет выделять дескрипторы без возможности восстановления, или к тому, что производительность сервера будет постепенно снижаться, что приведет к меньшей степени доступности приложения клиента. Очевидно, что таких сценариев развития событий следует избегать.

Рекомендации

Во введении особое внимание уделяется, что следует искать в управляемом коде, который выполняется на сервере, для увеличения стабильности и надежности платформы. Все эти проверки являются хорошей практической работой, но кроме того, их наличие на сервере должно быть обязательным.

Если возможна взаимоблокировка или ограничение ресурса, SQL Server остановит поток или разорвет AppDomain. Если это произойдет, то гарантируется выполнение только кода отката в области ограничения исполнения.

Используйте SafeHandle во избежание утечки ресурсов

В случае выгрузки AppDomain нельзя полагаться на выполнение блоков finally или методов завершения, так что очень важным является отделить весь доступ к ресурсам операционной системы с помощью класса SafeHandle, а не класса IntPtr, HandleRef или похожих классов. Это позволит отслеживать и закрывать в среде CLR дескрипторы, которые используются даже в случае разрыва AppDomain. Дескриптор SafeHandle будет использовать критический метод завершения, который всегда будет выполняться в среде CLR.

Дескриптор операционной системы сохраняется в безопасном дескрипторе с момента его создания до момента его освобождения. Ни в одном окне не может возникнуть ThreadAbortException, приводящее к утечке дескриптора. Кроме того, вызов платформы приведет к подсчету ссылки для дескриптора, что позволит более точно отслеживать жизненный цикл дескриптора, предотвращая проблему безопасности, связанную с состоянием гонки между Dispose и методом, использующим этот дескриптор в настоящий момент.

Для большинства классов, использующих метод завершения для простой очистки дескриптора операционной системы, больше не потребуется метод завершения. Вместо этого метод завершения будет присутствовать в производном классе SafeHandle.

Обратите внимание, что SafeHandle не является заменой IDisposable.Dispose. При явном удалении ресурсов операционной системе существует вероятность конфликта ресурсов и улучшения производительности. Помните, что блоки finally, явно удаляющие ресурсы, могут не выполняться до завершения.

SafeHandle позволяет реализовывать собственный метод ReleaseHandle, который будет выполнять освобождение дескриптора, например путем передачи состояния в процедуру освобождения дескриптора операционной системы ил посредством циклического освобождения набора дескрипторов. В среде CLR гарантируется выполнение этого метода. Автор реализации ReleaseHandle несет ответственность за освобождение дескриптора во всех случаях. Если этого не сделать, будет происходить утечка дескриптора, что приведет к утечке собственных ресурсов, связанных с дескриптором. Поэтому очень важно структурировать производные классы SafeHandle таким образом, чтобы реализация ReleaseHandle не нуждалась в выделении для нее ресурсов, недоступных в момент вызова. Обратите внимание, что разрешается вызывать методы, которые могут завершиться со сбоем в рамках реализации ReleaseHandle, если в коде могут обрабатываться подобные сбои, а контакт может быть завершен для освобождения собственного дескриптора. В целях отладки метод ReleaseHandle имеет возвращаемое значение Boolean, которое может быть равным false, если серьезная ошибка предотвратила освобождение ресурса. Это действие приведет к активации средства MDA releaseHandleFailed MDA, если оно включено, для более успешного определения проблемы. Если это не влияет на время выполнения никаким другим образом, метод ReleaseHandle не будет вызван для того же ресурса повторно и произойдет утечка дескриптора.

Дескриптор SafeHandle в некоторых контекстах не является подходящим. Так как метод ReleaseHandle может работать в потоке метода завершения GC, любые дескрипторы, которые должны быть освобождены в определенном потоке, не должны быть обернуты в SafeHandle.

Оболочки, которые могут быть вызваны во время выполнения, могут быть очищены в среде CLR без дополнительного кода. Код, использующий вызов платформы и обрабатывающий объект COM как IUnknown* или IntPtr, должен быть изменен для использования вызываемых оболочек времени выполнения. Дескриптор SafeHandle может не подойти в этом сценарии из-за возможности обратного вызова неуправляемым методом освобождения в управляемый код.

Правило анализа кода

Используйте SafeHandle для инкапсулирования ресурсов операционной системы. Не используйте HandleRef или поля типа IntPtr.

Предотвращайте запуск методов завершения для предупреждения утечки ресурсов операционной системы

Тщательно просмотрите методы завершения, чтобы убедиться, что даже если они не выполняются, критические ресурсы операционной системы не расходуются. В отличие от обычной выгрузки AppDomain при выполнении приложения в стабильном состоянии или при завершении работы сервера, такого как SQL Server, объекты не завершаются при внезапной выгрузке AppDomain. Убедитесь, что не происходит утечки ресурсов в случае внезапной выгрузки, так как невозможно гарантировать правильность работы приложения, однако целостность сервера должна поддерживаться именно посредством предотвращения утечки ресурсов. Используйте SafeHandle для освобождения любых ресурсов операционной системы.

Предотвращайте запуск предложений finally для предупреждения утечки ресурсов операционной системы

Не гарантируется выполнение предложений finally вне среды CER, поэтому разработчики библиотек не полагаются на код в блоке finally для освобождения неуправляемых ресурсов. Рекомендуемым решением является использование дескриптора SafeHandle.

Правило анализа кода

Для очистки системных ресурсов используйте SafeHandle вместо Finalize. Не используйте IntPtr; для инкапсулирования ресурсов используйте дескриптор SafeHandle. Если предложение finally должно выполняться, разместите его в среде CER.

Все блокировки должны проходить через существующий управляемый код блокировки

В среде CLR должно быть известно, заблокирован ли код, чтобы знать, когда следует разорвать AppDomain, а не отменять прерывать поток. Прерывание потока опасно тем, что данные, которые обрабатываются в потоке, могут быть оставлены в несогласованном состоянии. Поэтому весь AppDomain должен быть использован повторно. Неспособность определить блокировку может привести к взаимоблокировкам или неверным результатам. Используйте методы BeginCriticalRegion и EndCriticalRegion для определения областей блокировки. В классе Thread существуют статические методы, которые применимы только к текущему потоку, что способствует предотвращению изменения одним потоком счетчика блокировок другого потока.

В методах Enter и Exit такое CLR-уведомление уже является встроенным, поэтому их использование рекомендуется наряду с Оператор lock (Справочник по C#), использующим эти методы.

Другие механизмы блокировки, такие как спин-блокировки и AutoResetEvent, должны вызывать эти методы для уведомления среды CLR о входе в критический раздел. Эти методы не выполняют блокировок; они уведомляют среду CLR, что код выполняется в критическом разделе, поэтому прерывание потока может сделать общее состояние несогласованным. При определении собственного типа блокировки, например пользовательского класса ReaderWriterLock, используйте эти методы подсчета блокировок.

Правило анализа кода

Обозначьте и определите все блокировки при помощи методов BeginCriticalRegion и EndCriticalRegion. Не используйте в цикле перегрузки CompareExchange, Increment и Decrement. Не выполняйте вызов платформ вариантов Win32 этих методов. Не используйте перегрузку Sleep в цикле. Не используйте зависимые поля.

Код очистки должен находиться в блоках finally или catch, при этом не следуя за блоком catch

Код очистки никогда не должен следовать за блоком catch; он должен находиться в самом блоке finally или catch. Такой подход является рекомендуемым. Как правило, предпочтение отдается блоку finally, потому что в нем выполняется тот же код и при создании исключения, и при достижении конца блока try. В случае неожиданного создания исключения, например исключения ThreadAbortException, этот код очистки запущен не будет. В идеале для любых неуправляемых ресурсов, которые должны быть очищены в блоке finally, оболочкой должен служить дескриптор SafeHandle для предотвращения утечек. Обратите внимание, что ключевое слово C# using может быть эффективно использовано для удаления объектов, включая дескрипторы.

Несмотря на то, что повторное использование AppDomain может очистить ресурсы в потоке метода завершения, очень важно правильно разместить код очистки. Обратите внимание, что если поток получает асинхронное исключение без удерживания блокировки, в среде CLR происходит попытка завершения потока без повторного использования AppDomain. Своевременная очистка ресурсов является полезной, так как освобождает большое количество ресурсов и помогает лучше управлять жизненным циклом. Если явно не закрыть дескриптор файла по какому-либо ошибочному пути к коду, следует подождать очистки методом завершения SafeHandle; при следующем запуске кода можно не получить доступ к тому же самому файлу, если метод завершения еще не был запущен. По этой причине наличие кода очистки и его правильное выполнение будет способствовать более быстрому и эффективному восстановлению после сбоев, даже если это не является необходимостью.

Правило анализа кода

Код очистки после catch должен находиться в блоке finally. Разместите вызовы удаления в блоке finally. Блоки catch должны завершаться с созданием исключения или с повторным его созданием. Несмотря на то, что будут создаваться исключения, такие как определение в коде возможности установки сетевого подключения, что может привести к созданию дополнительных исключений, любой код, нуждающийся в перехвате некоторого количества исключений, при обычных обстоятельствах должен уведомлять о необходимости проверки кода для оценки правильности его работы.

Изменяемое общее состояние уровня процесса между доменами приложения должно быть устранено или должно использовать среду CER

Как было указано во введении, создание управляемого кода, надежно отслеживающего общее состояние уровня процесса между доменами приложения, может оказаться очень трудным. Общее состояние уровня процесса — это любой тип структуры данных, который совместно используется доменами приложения в коде Win32, в среде CLR или в управляемом коде с помощью удаленного взаимодействия. Очень трудно правильно записать любое изменяемое общее состояние в управляемом коде, а любое статическое общее состояние должно создаваться с большой осторожностью. При наличии общего состояния уровня процесса или компьютера следует найти способ удаления этого состояния или защиты его при помощи среды CER. Обратите внимание, что любая библиотека с общим состоянием, которое не было определено и исправлено, может привести к сбою в основном приложении, таком как SQL Server, нуждающемся в выгрузке AppDomain.

Если код использует COM-объект, избегайте общего использования этого COM-объекта в разных доменах приложения.

Блокировки не работают на уровне процесса или между доменами приложения.

В прошлом метод Enter и Оператор lock (Справочник по C#) использовались для создания глобальных блокировок процесса. Например, это происходит при блокировке гибких классов AppDomain, таких как экземпляры Type из сборок, не являющимися общими, объекты Thread, интернированные строки и некоторые строки, которые совместно используются в разных доменах приложения с помощью удаленного взаимодействия. Эти блокировки уже не относятся ко всему процессу. Чтобы распознать наличие блокировки уровня процесса между доменами приложения, определите, использует ли код в блокировки какие-либо внешний постоянный ресурс, такой как файл на диске или базу данных.

Обратите внимание, что блокировка в AppDomain может привести к возникновению проблем, если защищенный код использует внутренний ресурс, так как этот код может выполняться одновременно в различных доменах приложения. Это может стать проблемой при записи в один файл журнала или при привязке сокета для всего процесса. Эти изменения означают, что при использовании управляемого кода отсутствует простой способ получить глобальную блокировку процесса, кроме использования именованного класса Mutex или экземпляра Semaphore. Создайте код, который не выполняется в двух доменах приложения одновременно, или используйте классы Mutex или Semaphore. Если существующий код не может быть изменен, не используйте именованный мьютекс Win32 mutex для получения этой синхронизации, потому что выполнение в режиме волокон означает, что невозможно гарантировать получение и освобождение мьютекса одним и тем же потоком операционной системы. Необходимо использовать управляемый класс Mutex или именованный класс ManualResetEvent, AutoResetEvent или Semaphore для синхронизации блокировки кода способом, известным в среде CLR, вместо синхронизации блокировки с помощью неуправляемого кода.

Избегайте блокировки (typeof(MyType))

Также представляют проблему закрытые и открытые объекты Type в общих сборках только с одной копией кода, который используется в разных доменах приложения. Для общих сборок существует только один экземпляр Type на процесс. Это означает, что несколько доменов приложения используют один и тот же экземпляр Type. Блокировка экземпляра Type блокирует весь процесс, а не только AppDomain. Если блокируется один AppDomain для объекта Type, то поток прерывается внезапно и блокировка снята не будет. Затем эта блокировка может привести к взаимоблокировкам доменов приложения

Хорошим способом блокировки в статических методах является добавление в код статического внутреннего объекта синхронизации. Он может быть инициализирован в конструкторе класса, если таковой имеется. Однако если последний отсутствует, то этот объект можно инициализировать следующим способом:

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;
    }
}

Затем при блокировке используйте свойство InternalSyncObject, чтобы получить объект блокировки. Нет необходимости использовать это свойство, если в конструкторе класса был инициализирован внутренний объект синхронизации. Код инициализации двойной проверки блокировки должен быть похож на следующий пример:

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;
    }
}

Примечание о блокировке (this)

В целом, можно блокировать отдельный объект, который открыт для общего доступа. Однако если этот объект является объектом одноэлементного множества, который может привести к взаимоблокировке все подсистемы, рассмотрите возможность использования также приведенного выше шаблона разработки. Например, блокировка одного объекта SecurityManager может привести к взаимоблокировке с AppDomain, что приведет к невозможности использования всего AppDomain. Рекомендуется не блокировать общедоступные объекты этого типа. Однако блокировка отдельной коллекции или массива, как правило, не должна вызывать неполадок.

Правило анализа кода

Не блокируйте типы, которые могут использоваться в различных доменах приложения или которые не имеют строгой идентификации. Не вызывайте метод Enter для объектов Type, MethodInfo, PropertyInfo, String, ValueType, Thread или любого объекта, производного из MarshalByRefObject.

Удаляйте вызовы GC.KeepAlive

В большей части существующего кода метод KeepAlive не используется, когда это необходимо, или же используется, когда это неприемлемо. После преобразования в SafeHandle классы могут не вызывать метод KeepAlive, если они не имеют метод завершения, но полагаются на дескриптор SafeHandle для завершения дескрипторов операционной системы. Хотя снижение производительности при сохранении вызова метода KeepAlive является практически незаметным, ощущение необходимости или достаточности вызова метода KeepAlive для решения связанного с жизненным циклом вопроса, который может и не являться актуальным, делает поддержку кода более сложной. Однако при использовании вызываемых оболочек CLR COM-взаимодействия метод KeepAlive все еще необходим для выполнения кода.

Правило анализа кода

Удалите KeepAlive.

Используйте атрибут защиты основного приложения

Атрибут защиты основного приложения HostProtectionAttribute обеспечивает использования декларативных действий безопасности для определения требований к защите основного приложения, что позволяет основному приложению предотвратить вызов определенных методов даже кодом с полным доверием. Эти методы, включающие as Exit или Show для SQL Server, не являются подходящими для данного основного приложения.

Атрибут защиты основного приложения влияет только на неуправляемые приложения, на которых размещена общеязыковая среда выполнения и которые осуществляют защиту основных приложений, таких как SQL Server. При применении данное действие, связанное с безопасностью, приводит к созданию запроса ссылки на основе ресурсов узла, предоставляемого классом или методом. Если код выполняется в клиентском приложении или на незащищенном сервере, атрибут "испаряется" — он не обнаруживается и, следовательно, не применяется.

ms228970.alert_caution(ru-ru,VS.90).gifВажное примечание.

Целью данного атрибута является назначение определенных узлом правил модели программирования, а не поведения системы безопасности. Несмотря на то, что запрос ссылки используется для проверки соответствия требованиям модели программирования, объект HostProtectionAttribute не является разрешением безопасности.

Если на узле нет требований модели программирования, запрос ссылки не возникает.

Данный атрибут имеет следующие идентификаторы:

  • Методы или классы, которые не соответствуют требованиям модели программирования узла, но, тем не менее, благоприятны.

  • Методы или классы, которые не соответствуют требованиям модели программирования узла и могут привести к дестабилизации работы управляемого сервером кода пользователя.

  • Методы или классы, которые не соответствуют требованиям модели программирования узла и могут привести к дестабилизации работы самого сервера.

ms228970.alert_note(ru-ru,VS.90).gifПримечание.

При создании библиотеки классов, которая должна вызываться приложениями, работающими в защищенной серверной среде, следует применять данный атрибут к элементам, предоставляющим категории ресурса HostProtectionResource. Элементы библиотеки классов .NET Framework с данным атрибутом определяют только непосредственно вызывающий модуль для проверки. Этот элемент библиотеки должен таким же образом определять проверку своего непосредственно вызывающего модуля.

Дополнительные сведения об атрибуте защиты основного приложения см. в разделе HostProtectionAttribute.

Правило анализа кода

Для SQL Server все методы, используемые для представления синхронизации или поточности, должны быть идентифицированы с помощью атрибута защиты основного приложения. Это включает методы, которые используют общее состояние, являются синхронизированными или управляют внешними процессами. Значения HostProtectionResource, которые влияют на SQL Server, включают SharedState, Synchronization и ExternalProcessMgmt. Однако любой метод, предоставляющий любой ресурс HostProtectionResource, должен быть определен с помощью атрибута защиты основного приложения, а не только те ресурсы, которые влияют на SQL.

Не устанавливайте блокировки в неуправляемом коде на неопределенное время

Блокирование в неуправляемом коде в отличие от управляемого кода может привести к атаке типа "отказ в обслуживании", так как среда CLR не сможет прервать поток. Заблокированный поток препятствует выгрузке AppDomain средой CLR без выполнения операций, которые особенно могут повредить безопасности. Блокирование с использованием примитива синхронизации Win32 — это явный пример действия, которое не должно быть разрешено. Блокирование вызова ReadFile для сокета можно по возможности избежать. В идеале интерфейс API Win32 должен предоставлять механизм истечения срока действия подобной операции.

Любой метод, вызывающий машинный код, в идеале должен использовать вызов Win32 с допустимым, конечным временем ожидания. Если пользователь может указать срок действия, этот пользователь не должен иметь возможность указывать неопределенное время ожидания без некоторых особых разрешений безопасности. Можно придерживаться следующего руководства: если метод будет блокировать поток более 10 секунд, необходимо использовать версию, которая поддерживает время ожидания, или требуется дополнительная поддержка среды CLR.

Here are some examples of problematic API’s. Каналы (как анонимные, так и именованные) могут быть созданы с учетом времени ожидания; однако необходимо удостовериться, что код никогда не вызывает CreateNamedPipe или WaitNamedPipe с NMPWAIT_WAIT_FOREVER. Кроме того, может возникнуть неожиданное блокирование, даже если указано время ожидания. Вызов WriteFile для анонимного канала приведет к блокировке до записи всех байтов. Это означает, что если в буфере содержатся непрочитанные данные, вызов WriteFile приведет к блокировке до тех пор, пока средство чтения не освободит пространство в буфере канала. Сокеты всегда должны использовать какой-либо интерфейс API, который учитывает механизм времени ожидания.

Правило анализа кода

Блокировка без времени ожидания в неуправляемом коде является атакой типа "отказ в обслуживании". Не выполняйте вызовов платформы WaitForSingleObject, WaitForSingleObjectEx, WaitForMultipleObjects, MsgWaitForMultipleObjects и MsgWaitForMultipleObjectsEx. Не используйте NMPWAIT_WAIT_FOREVER.

Укажите любые функциональные возможности, зависящие от однопотокового подразделения.

Укажите любой код, который использует однопотоковые подразделения COM. Однопотоковые подразделения отключены в процессе SQL Server. Функциональные возможности, которые зависят от CoInitialize, такие как счетчики производительности или буфер обмена, должны быть отключены на SQL Server.

Убедитесь, что в методах завершения отсутствуют проблемы, связанные с синхронизацией

В будущих версиях .NET Framework могут существовать несколько потоков метода завершения, то есть методы завершения для различных экземпляров одного типа могут выполняться одновременно. Они не должны быть абсолютно потокобезопасны, с помощью сборщика мусора гарантируется, что только один поток будет запускать метод завершения для данного экземпляра объекта. Однако методы завершения должны быть закодированы, чтобы избежать состояний гонки и взаимоблокировок при одновременном выполнении в различных экземплярах объектов. При использовании в методе завершения любого внешнего состояния, например записи в файл журнала, необходимо обработать любые проблемы, связанные с поточностью. Не полагайтесь на предоставление потокобезопасности в методах завершения. Не используйте локальное хранилище потока, управляемое или собственное, для хранения состояния потока метода завершения.

Правило анализа кода

Методы завершения не должны иметь проблем с синхронизацией. Не используйте статическое изменяемое состояние в методе завершения.

По возможности избегайте наличия неуправляемой памяти

Утечка неуправляемой памяти может происходить так же, как и утечка дескриптора операционной системы. По возможности пытайтесь использовать память в стеке с помощью stackalloc (Справочник по C#) или закрепленный управляемый объект, такой как Оператор fixed (Справочник по C#) или GCHandle, с использованием byte[]. В конечном счете, это будет очищено GC. Однако если необходимо выделить неуправляемую память, рассмотрите возможность использования класса, который является производным из SafeHandle, для обертывания выделенной памяти.

Обратите внимание, что имеется, по крайней мере, один случай, в котором дескриптор SafeHandle не будет являться подходящим. Для COM-вызовов методов, которые распределяют или освобождают память, одна библиотека DLL, как правило, выделяют память с помощью CoTaskMemAlloc, затем другая библиотека DLL освобождает эту память с помощью CoTaskMemFree. Использование SafeHandle в этих местах не является допустимым, так как этот дескриптор будет пытаться привязать жизненный цикл неуправляемой памяти к жизненному циклу дескриптора SafeHandle вместо передачи управления жизненным циклом памяти другой библиотеке DLL.

Просмотрите все использования Catch(Exception)

Блоки catch, которые перехватывают все исключения вместо одного определенного исключения, теперь будут перехватывать также и асинхронные исключения. Рассмотрите каждый блок catch(Exception), пытаясь отыскать возможно пропущенный код освобождения ресурса или отката, а также возможно неверное поведение в блоке catch для обработки ThreadAbortException, StackOverflowException или OutOfMemoryException. Обратите внимание, что в этом коде может регистрироваться или предполагаться обнаружения только определенных исключений, а также что при возникновении исключения код завершается с ошибкой только по какой-либо одной причине. Эти предположения могут быть обновлены для включения ThreadAbortException.

Рассмотрите изменение всех расположений перехвата всех исключений для перехвата определенного типа исключения, которое, как предполагается, будет создаваться, например исключение FormatException из методов формата строки. Это предотвращает блок catch от выполнения при неожиданных исключениях, а также способствует тому, чтобы в коде не скрывались недоработки посредством перехвата неожиданных исключений. Вообще, никогда не обрабатывайте исключение в коде библиотеки (код, который нуждается в перехвате исключения, может обозначать на ошибку проектировки в вызываемом коде). В некоторых случаях может потребоваться перехватывать исключение и создавать другой тип исключения для предоставления дополнительных данных. В этом случае используйте вложенные исключения, сохраняя фактическую причину ошибки в свойстве InnerException нового исключения.

Правило анализа кода

Просмотрите все блоки catch в управляемом коде, которые служат для перехвата всех объектов или перехвата всех исключений. В C# это означает отметку посредством флагов как catch {}, так и catch(Exception) {}. Рассмотрите возможность создания вполне конкретного типа исключения или просмотрите код, чтобы убедиться в том, что он не функционирует недопустимым образом при перехвате типа неожиданного исключения.

Не предполагайте, что управляемый поток является потоком Win32 — он является волокном

Использование локального хранилища управляемого потока функционирует правильно, однако невозможно использовать локальное хранилище неуправляемого кода или предположить, что код сможет быть повторно запущен в текущем потоке операционной системы. Не следует изменять такие параметры, как языковой стандарт потока. Не вызывайте InitializeCriticalSection или CreateMutex посредством вызова платформы, потому что эти объекты нуждаются в потоке операционной системы, который входит в блокировку и выходит из нее. Так как это неприменимо при использовании волокон, критические области и мьютексы Win32 не могут быть использованы в SQL напрямую. Обратите внимание, что управляемый класс Mutex не обрабатывает эти вопросы, связанные с соответствием потоков.

Можно безопасно использовать большую часть состояния управляемого объекта Thread, включая управляемое локальное хранилище потока и текущий язык и региональные параметры пользовательского интерфейса потока. Также можно использовать атрибут ThreadStaticAttribute, который предоставляет доступ к значению существующей статической переменной только текущему управляемому потоку (это еще один способ организации локального хранилища волокон в среде CLR). По причинам, связанным с моделью программирования, нельзя изменять текущий язык и региональные параметры потока при работе в SQL.

Правило анализа кода

SQL Server работает в режиме волокон; не используйте локальное хранилище потока. Избегайте вызовов платформы TlsAlloc, TlsFree, TlsGetValue и TlsSetValue.

Позволяйте SQL Server обрабатывать олицетворение

Так как олицетворение происходит на уровне потока и SQL может работать в режиме волокон, в управляемом коде пользователи не должны олицетворяться, также в управляемом коде не следует вызывать RevertToSelf.

Правило анализа кода

Позволяйте SQL Server обрабатывать олицетворение. Не используйте RevertToSelf, ImpersonateAnonymousToken, DdeImpersonateClient, ImpersonateDdeClientWindow, ImpersonateLoggedOnUser, ImpersonateNamedPipeClient, ImpersonateSelf, RpcImpersonateClient, RpcRevertToSelf, RpcRevertToSelfEx или SetThreadToken.

Не вызывайте Thread::Suspend

Возможность приостановления потока может показаться простой операцией, однако это может приводить к взаимоблокировкам. Если поток, удерживающий блокировку, приостанавливается вторым потоком, а затем второй поток пытается выставить ту же самую блокировку, возникнет взаимоблокировка. Метод Suspend может отрицательно повлиять на безопасность, загрузку класса, удаленное взаимодействие и отражение.

Правило анализа кода

Не вызывайте метод Suspend. Рассмотрите возможность использования вместо этого настоящего примитива синхронизации, такого как a Semaphore или ManualResetEvent.

Защищайте критически важные операции с помощью среды CER и контрактов о надежности

При выполнении сложной операции, обновляющей общее состояние или нуждающейся в определении полностью успешного выполнения или полного сбоя, убедитесь, что эта операция защищена средой CER. Это гарантирует выполнение кода в любом случае, даже при неожиданном прерывании потока или неожиданной выгрузке AppDomain.

Среда CER является определенным блоком try/finally, перед которым идет вызов метода PrepareConstrainedRegions.

Это оповещает JIT-компилятор о необходимости подготовки всего кода в блоке finally до выполнения блока try. Это гарантирует создание кода в блоке finally и выполнение его во всех случаях. Часто в среде CER имеется пустой блок try. Использование среды CER защищает от прерывания асинхронного потока и создания исключений нехватки памяти. Форму среды CER, которая дополнительно обрабатывает переполнение стека для очень распространенного кода см. в описании метода ExecuteCodeWithGuaranteedCleanup.

См. также

Основные понятия

Программирование SQL Server и атрибуты защиты основного приложения

Ссылки

System.Runtime.ConstrainedExecution