В продолжение темы устойчивых приложений и CER (начало тут) — рассмотрим, как подготовить произвольный (или почти произвольный) участок кода для выполнения в регионе Constrained Execution.
Зачем такое надо? Помните, я говорил, что в процессе подготовки к CER происходит инспекция стека и пре-jitt. Но проинспектировать стек, если вызов происходит через интерфейс или виртуальный метод заранее невозможно. Поэтому такой код:
Для того, чтобы подготовить виртуальные методы, необходимо воспользоваться еще одной возможностью класса RuntimeHelpers — методом PrepareMethod():
Думаю, вы уже поняли, что в случае вызова через интерфейс следует подготовить методы всех классов, которые могут скрываться за интерфейсом. Sad but true.
Для делегатов процедура выглядит аналогично, однако следует помнить вот что — PrepareDelegate работает только по первому делегату в списке — это означает, что в MulticastDelegate будет обработан только первый из делегатов в списке — нужно будет вручную проходит по всем делегатам из списка и готовить их.
Если вы используете делегаты только для событий, вызывая события из CER, то ситуация несколько упрощается — использование кода, приведенного ниже, позволяет автоматически подготавливать делегаты:
Если вы попробуете применить этот аттрибут, то вы увидите, что у него можно установить два свойства из допустимых перечислений: Cer и ConsistencyGuarantee. ConsistencyGuarantee отвечает за то, какие «разрушения» может нанести метод, если забить на асинхронные exceptions при исполнении его под Cer. Так выглядит его объявление (тут не нужно переводить, я думаю):
Несмотря на большое количество сочетаний этих свойств, для методов, с которых начинается исполнение CER, валидны только 3 комбинации:
— [ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)] — «моя хата с краю, ничего не знаю»; при возникновении ошибки кроме инстанса объекта ничего не пострадает.
— [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] — «чистое» падение: даже если упал, можно продолжать работу;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] — «метод точно не упадет»
По умолчанию все методы считаются имеющими аттрибут [ReliabilityContract(Consistency.WillCorruptProcess, Cer.None)].
Эти контракты на выполнение будут интерпретироваться хостинг-средой фреймворка, чтобы отреагировать на проблему соответствующим образом — например, «прибить» домен заранее, если уже произошло что-то фатальное, не давая программе повредить что-либо еще.
Напоследок вот что. acerv спрашивал про StackOverflow в прошлом посте. "- Спрашивали? Отвечаем!" — .net framework не гарантирует, что поток выполнит какой-либо блок finally в случае StackOverflowException. Тем не менне, существует не очень красивая, но простая возможность гарантированно выполнить некоторый clean-up код — все при помощи того же RuntimeHelpers. Много объяснять не буду:
Надеюсь, было интересно.
Однако ну и длинной выдалась тема! :) А еще есть что сказать про SafeHandles, MemoryBarries и FailFast. Ждите :)
Зачем такое надо? Помните, я говорил, что в процессе подготовки к CER происходит инспекция стека и пре-jitt. Но проинспектировать стек, если вызов происходит через интерфейс или виртуальный метод заранее невозможно. Поэтому такой код:
скомпилируется, но в run-time не достигнет желаемого.... RuntimeHelper.PrepareConstrainedRegions(); try {} finally { obj.SomeVirtualMethod(); }
Для того, чтобы подготовить виртуальные методы, необходимо воспользоваться еще одной возможностью класса RuntimeHelpers — методом PrepareMethod():
Теперь регион будет подготовлен и отработает нормально.... RuntimeHelpers.PrepareMethod(obj.GetType().GetMethod("SomeVirtualMethod").MethodHandle); RuntimeHelpers.PrepareConstrainedRegions(); try {} finally { obj.SomeVirtualMethod(); }
Думаю, вы уже поняли, что в случае вызова через интерфейс следует подготовить методы всех классов, которые могут скрываться за интерфейсом. Sad but true.
Для делегатов процедура выглядит аналогично, однако следует помнить вот что — PrepareDelegate работает только по первому делегату в списке — это означает, что в MulticastDelegate будет обработан только первый из делегатов в списке — нужно будет вручную проходит по всем делегатам из списка и готовить их.
Если вы используете делегаты только для событий, вызывая события из CER, то ситуация несколько упрощается — использование кода, приведенного ниже, позволяет автоматически подготавливать делегаты:
Понятно, что с большой силой приходит и большая ответственность. А возможность выполнить любой код без ограничения и выброса асинхронных исключений — это большая сила. Инфраструктура .net позволяет разработчику указать, какую ответственность принимает на себя код, будучи запущенным под CER. И делает он это при помощи аттрибута System.Runtime.ConstrainedExecution.ReliabilityContractAttribute. Этот аттрибут может быть применен как на уровне сборки или класса, так и на уровне метода, обеспечивая необходимый уровень детальности.public event EventHandler MyEvent { add { if (value == null) return; RuntimeHelpers.PrepareDelegate(value); lock(this) _myEvent += value; } remove { lock(this) _myEvent -= value; } }
Если вы попробуете применить этот аттрибут, то вы увидите, что у него можно установить два свойства из допустимых перечислений: Cer и ConsistencyGuarantee. ConsistencyGuarantee отвечает за то, какие «разрушения» может нанести метод, если забить на асинхронные exceptions при исполнении его под Cer. Так выглядит его объявление (тут не нужно переводить, я думаю):
А Cer отвечает за то, насколько успешным будет вызов метода. Он может принимать значенияpublic enum Consistency { MayCorruptProcess = 0, MayCorruptAppDomain = 1, MayCorruptInstance = 2, WillNotCorruptState = 3 }
{ None = 0, MayFail = 1, Success = 2 }
Несмотря на большое количество сочетаний этих свойств, для методов, с которых начинается исполнение CER, валидны только 3 комбинации:
— [ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)] — «моя хата с краю, ничего не знаю»; при возникновении ошибки кроме инстанса объекта ничего не пострадает.
— [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] — «чистое» падение: даже если упал, можно продолжать работу;
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] — «метод точно не упадет»
По умолчанию все методы считаются имеющими аттрибут [ReliabilityContract(Consistency.WillCorruptProcess, Cer.None)].
Эти контракты на выполнение будут интерпретироваться хостинг-средой фреймворка, чтобы отреагировать на проблему соответствующим образом — например, «прибить» домен заранее, если уже произошло что-то фатальное, не давая программе повредить что-либо еще.
Напоследок вот что. acerv спрашивал про StackOverflow в прошлом посте. "- Спрашивали? Отвечаем!" — .net framework не гарантирует, что поток выполнит какой-либо блок finally в случае StackOverflowException. Тем не менне, существует не очень красивая, но простая возможность гарантированно выполнить некоторый clean-up код — все при помощи того же RuntimeHelpers. Много объяснять не буду:
try { RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup( TryMethod, CleanupMethod, this); } catch(SomeException exc) { ... } // Handle unexpected failure ... [MethodImpl(MethodImplOptions.NoInlining)] void TryMethod(object userdata) { ... } [MethodImpl(MethodImplOptions.NoInlining)] [ReliabilityContract(Cer.Success, Consistency.MayCorruptInstance)] void CleanupMethod(object userdata, bool fExceptionThrown) { ... }
finally
Надеюсь, было интересно.
Однако ну и длинной выдалась тема! :) А еще есть что сказать про SafeHandles, MemoryBarries и FailFast. Ждите :)