Изучение ThreadAbortException с помощью Rotor, Крис Селлз
Потоки и исключения
static void Foo() {
throw new Exception("oops!");
Console.WriteLine("Never going to get here...");
}
static void Main(string[] args) {
try {
Foo();
Console.WriteLine("Never going to get here, either...");
}
catch( Exception ex ) {
Console.WriteLine("Exceptions happen: " + ex.Message);
}
}
static void Foo() {
try { while( true ) { ... } }
catch( ThreadAbortException ex ) { ... }
finally {...}
// Will never get here if thread aborted
}
static void Main(string[] args) {
Thread thread = new Thread(new ThreadStart(Foo));
thread.Start();
thread.Abort(); // cause ThreadAbortException to be thrown
}
Детали реализации
- 1. Приостанавливает прерываемый поток операционной системы. 2. Устанавливает бит AbortRequested .NET потока. 3. Ожидает перехода прерываемого потока в состояние, в котором возможно его прерывание (interruptible state), путем вызова функций Sleep, Join или wait-функций. 4. Добавляет APC-вызов (Asynchronous procedure call) в очередь APC потока (используя Win32 функцию QueueUserAPC) и возобновляет выполнение потока. 5. Когда прерываемый поток переходит в тревожное состояние (alertable wait state), планировщик вызвает обработчик APC, который устанавливает состояние прерываемого потока в AbortInitiated. Поток переходит в тревожное сотояние только при передаче TRUE в качестве значения bAlertable в функцию типа SleepEx. Если этого не происходит, очередь APC не обработает запрос, а это значит, что вызов ThreadAbort не гарантированно приведет к генерации исключения ThreadAbortException в прерываемом потоке. 6. Когда Common Language Runtime (CLR) возвращает управление прерываемому потоку через «обходную» (“trip”) функцию, которая проверяет все возможные состояния потока для специальных действий, включая будет ли поток прерван. 7. Если состояние потока установлено в AbortInitiated, генерируется исключение ThreadAbortEception, которое может быть обработано (или не обработано) прерываемым потоком в блоках catch и/или finally.
Вызов Thread.Abort приводит к установке CLR определенного флага прерываемого потока, затем этот флаг проверяется в определенных контрольных точках жизни потока, после чего генерируется исключение, если этот флаг был выставлен.
Как я это выяснил
Я выяснил как Thread.Abort приводит к генерации исключения из одного потока в другой путем загрузки и тщательного изучения исходного кода Rotor [1]. Хотя потоки являются низкоуровневыми примитивами (в отличие от WinForms или ASP.NET), исходный код Rotor содержит реализацию и позволяет нам понять, как на самом деле работают те или иные вещи (например, что происходит когда прерывается поток, выполняющий блоки catch или finally).
namespace System.Threading {
public sealed class Thread {
...
public void Abort() { AbortInternal(); }
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern void AbortInternal();
}
}
static
ECFunc gThreadFuncs[] = {
...
{FCFuncElement("AbortInternal", NULL, (LPVOID)ThreadNative::Abort)},
...
};
Где же мы сейчас?
Генерация исключения из одного потока в другой работает прекрасно, поскольку CLR заботиться обо всем за нас и не требует реализации этой возможности собственными силами (как в случае работы с Win32). Это еще один пример виртуализации платформы на пути к обеспечению сервисов, которые не предоставляются нижележащей операционной системой. И как я это все выяснил? Все это я выяснил с помощью лучшей документации любого программного обеспечения, имеется ввиду, конечно же, исходный код.Ссылки
- Shared Source CLI 1.0 Release (AKA Rotor source code) "Rotor: Shared Source CLI Provides Source Code for a FreeBSD Implementation of .NET," Jason Whittington, MSDN Magazine, July 2002 Shared Source CLI Essentials, David Stutz et al, O'Reilly and Associates, March 2003 (est.)
Я ошибся в комментарии, в котором приведен перечень условий, при котором может быть прерван поток.
ОтветитьУдалитьНа самом деле поток может быть прерван в одном из следующих случаях (Joe Duffy):
We don’t process thread aborts if you’re executing inside a Constrained Execution Region (CER), finally block, catch block, .cctor, or while running inside unmanaged code. If an abort is requested while inside one of these regions, we process it as soon as you exit (assuming you’re not nested inside another).
Сергей, отличный пост.
ОтветитьУдалитьКак в общем-то практически все посты в данном блоге.
Нравится на мой взгляд правильно выбранный уровень описываемых задач - не слишком простой и не слишком сложный (который бы 90% программистов-среднячков не поняли бы). Т.е. читать интересно и полезно.
Становлюсь постоянным читателем. Продолжай в том же духе!
PS. Надо прикрутить защиту от спама.
Кстати, комментарий про то в каких случаях код не прерывается по ThreadAbortException - тоже очень полезный.
ОтветитьУдалитьМда, спам я иногда пропускаю:) Спасибо за напоминание, как-нить обязательно прикручу от него что-либо.
ОтветитьУдалитьИ спасибо за отзывы! И вообще, приятно видеть знакомых rsdn-еров у себя на блоге!