Ограниченные области выполнения

Пример из предыдущего раздела демонстрирует некоторый уровень паранойи, который следует допускать для написания “пуленепробиваемого” нейтрального к исключениям кода. Чтобы предотвратить исключение, связанное с переполнением стека, перед вызовом ListSwap даже было выделено необходимое дополнительное пространство.

Может показаться, что все неприятности учтены. К сожалению, это не так. В среде CLR могут случаться другие асинхронные исключения, такие как ThreadAbortException, OutOfMemoryException и StackOverf lowException.

Например, что если на фазе фиксации изменений метода TerminateEmployee домен приложения будет остановлен, вызвав исключение ThreadAbortException? Или что если во время первого вызова ListSwap компилятор JIT не сможет выделить достаточно памяти для первоначальной компиляции метода? Ясно, что с такой неприятной ситуацией справиться нелегко.

Фактически во времена .NET 1.1 в таких случаях мало, что можно было сделать. Однако, начиная с .NET 2.0, можно воспользоваться ограниченной областью выполнения (Constrained Execution Region — CER) или критичным финализатором.
CER представляет собой участок кода, который среда CLR подготавливает до выполнения, так что когда в нем возникает потребность, все необходимое имеется под рукой, и вероятность сбоя минимальна. Более того, CLR откладывает доставку любых асинхронных исключений вроде ThreadAbortException на время выполнения кода CER.

Работа с CER осуществляется с использованием класса RuntimeHelpers из пространства имен System.Runtime.CompilerServices. Чтобы создать CER, просто вызовите RuntimeHelpers.PrepareConstrainedRegions перед оператором try. CLR затем проверит блоки catch/finally и подготовит их, пройдя по графу вызовов, чтобы убедиться, что все вызываемые в нем методы уже JIT-скомпилированы и необходимое пространство стека доступно. Несмотря на то что PrepareConstrainedRegions вызывается до оператора try, действительный код внутри блока try не подготовлен.

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

1
2
3
4
5
6
7
RuntimeHelpers.PrepareConstrainedRegions();
try {
}
finally
{
// здесь должен быть помещен критичный код
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System.Collections;
using System. Runtime. CompilerServices ;
using System.Runtime.ConstrainedExecution;
class Employee
{
}
class EmployeeDatabase {
public void TerminateEmployee ( int index )  {
// Клонировать важные объекты.
ArrayList tempActiveEmployees =
(ArrayList) activeEmployees.Clone ();
ArrayList tempTerminatedEmployees =
(ArrayList) terminatedEmployees.Clone ();
// Выполнить действия над временными объектами,
object employee = tempActiveEmployees[index];
tempActiveEmployees.RemoveAt( index );
tempTerminatedEmployees.Add( employee );
RuntimeHelpers.PrepareConstrainedRegions();
try {} finally {
// Зафиксировать изменения.
ArrayList tempSpace = null;
ListSwap ( ref activeEmployees,
ref tempActiveEmployees,
ref tempSpace );
ListSwap( ref terminatedEmployees,
ref tempTerminatedEmployees,
ref tempSpace ) ;
}
}
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.Success ) ]
void ListSwap( ref ArrayList first,
ref ArrayList second, ref ArrayList temp )  {
temp = first;
first = second;
second = temp;
temp - null;
}
private ArrayList activeEmployees;
private ArrayList terminatedEmployees;

Обратите внимание, что раздел фиксации в методе TerminateEmployee упакован в CER. Во время выполнения, прежде чем запустить этот код, среда CLR подготовит его, также подготавливая метод ListSwap и убедившись, что стек может обслужить эту работу.

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

Этот атрибут информирует исполняющую систему, какого рода гарантии предоставляет метод ListSwap, чтобы правильно сформировать CER. Атрибут ReliabilityContractAttribute можно также присоединить и к методу TerminateEmployee, но это полезно только для кода, выполняемого внутри CER. Если хотите снабдить этим атрибутом метод TerminateEmployee, чтобы его можно было вызывать внутри CER, определенного где-либо, добавьте упомянутый атрибут следующим образом:

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]

Атрибут ReliabilityContractAttribute выражает основную цель, которая ставится перед TerminateEmployee. Поскольку Consistency установлено в Consistency. WillNotCorruptState, то несмотря на то, что сбой может произойти, состояние системы не будет повреждено.

К другим значениям, которые можно указать, относятся Consistency.MayCorruptProcess, Consistemcy.MayCorruptAppDomain и Consistency .MayCorruptInstance. Имена констант говорят сами за себя, а дополнительную информацию можно получить в документации MSDN, но для более устойчивого программного обеспечения очевидно, что предпочтение будет отдано контракту надежности Consistemcy.WillNotCorruptState.

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

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

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100