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


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

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

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

  • Не допускайте утечек ресурсов операционной системы.

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

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

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

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

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

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

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

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

Условия нехватки памяти не являются редкостью в SQL Server.

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

Правила наилучшей практики

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

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

Использование 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, если будет обнаружена катастрофическая ошибка, препятствующая освобождению ресурса. Это приведет к активации releaseHandleFailed MDA, если она включена, чтобы помочь выявить проблему. Это никак не повлияет на время выполнения; ReleaseHandle не будет вызываться повторно для того же ресурса, и, следовательно, дескриптор утечёт.

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

Вызываемые оболочки среды выполнения (RCW) можно очистить с помощью среды CLR без дополнительного кода. Для кода, использующего Platform Invoke и обрабатывающего COM-объект как IUnknown* или IntPtr, код должен быть переиспользован для использования RCW. SafeHandle может оказаться недостаточным для этого сценария из-за возможности вызова неуправляемого метода с обращением к управляемому коду.

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

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

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

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

Убедитесь, что блоки finally не должны выполняться, чтобы предотвратить утечку ресурсов операционной системы.

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

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

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

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

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

Enter и Exit встроено это уведомление CLR, поэтому их использование рекомендуется, а также использование инструкции блокировки, которая использует эти методы.

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

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

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

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

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

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

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

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

Process-Wide изменяемое общее состояние между доменами приложений следует устранить или использовать область ограниченного выполнения.

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

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

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

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

Обратите внимание, что блокировка в пределах AppDomain может вызвать проблемы, если защищенный код использует внешний ресурс, так как этот код может выполняться одновременно в нескольких доменах приложений. Это может быть проблемой при записи в один файл журнала или при привязке к сокету для всего процесса. Эти изменения означают, что нет простого способа использования управляемого кода для получения глобальной блокировки процесса, кроме использования именованного Mutex или Semaphore экземпляра. Создайте код, который не выполняется одновременно в двух доменах приложений, или используйте классы Mutex или Semaphore. Если существующий код не может быть изменен, не используйте именованный мьютекс Win32 для достижения этой синхронизации, так как выполнение в режиме файбера означает, что вы не можете гарантировать, что тот же поток операционной системы получит и выпустит мьютекс. Для синхронизации блокировки кода вы должны использовать управляемый класс 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;
    }
}

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

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

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

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

Удалить вызовы GC.KeepAlive

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

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

Удалите KeepAlive.

Использование атрибута HostProtection

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

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

Это важно

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

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

Этот атрибут определяет следующее:

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

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

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

Замечание

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

Дополнительные сведения о HPA см. в HostProtectionAttribute.

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

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

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

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

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

Ниже приведены некоторые примеры проблемных API. Каналы (как анонимные, так и именованные) можно создать с тайм-аутом; однако код должен гарантировать, что он никогда не вызывает функции CreateNamedPipe и WaitNamedPipe с NMPWAIT_WAIT_FOREVER. Кроме того, может возникнуть непредвиденная блокировка, даже если задано время ожидания. Вызов WriteFile анонимного канала будет блокироваться до тех пор, пока не будут записаны все байты, то есть если буфер имеет непрочитанные данные в нем, WriteFile вызов будет блокироваться, пока читатель не освободит место в буфере канала. Сокеты всегда должны использовать некоторый API, который учитывает механизм времени ожидания.

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

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

Определите любые функции STA-Dependent

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

Убедитесь, что финализаторы не содержат проблем синхронизации.

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

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

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

Избегайте неуправляемой памяти, если это возможно

Неуправляемая память может утекать, как дескриптор операционной системы. По возможности попробуйте использовать память в стеке с помощью stackalloc или закрепленного управляемого объекта, например фиксированной инструкции или GCHandle с помощью байта[]. В конечном итоге GC очищает их. Однако если необходимо выделить неуправляемую память, подумайте об использовании класса, производного от SafeHandle, чтобы обернуть выделение памяти.

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

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

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

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

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

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

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

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

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

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

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

Разрешить SQL Server обрабатывать олицетворение

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

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

Пусть SQL Server обрабатывает имперсонацию. Не используйте RevertToSelf, ImpersonateAnonymousTokenDdeImpersonateClientImpersonateDdeClientWindowImpersonateLoggedOnUserImpersonateNamedPipeClientImpersonateSelfRpcImpersonateClientRpcRevertToSelfRpcRevertToSelfExили .SetThreadToken

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

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

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

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

Защита критически важных операций с ограниченными регионами выполнения и контрактами надежности

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

CER — это определенный try/finally блок, который непосредственно предшествует вызову PrepareConstrainedRegions.

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

См. также