С# - исключение прерывания потока (исключение прерывания потока), реконструирующее себя

У меня есть текущий код:

class Program
{
    private static void Main()
    {
        while (true)
        {

            try
            {
                Thread.CurrentThread.Abort();
            }
            catch (ThreadAbortException)
            {
                Console.WriteLine("Abort!");
                Thread.ResetAbort();
            }


            Console.WriteLine("now waiting");
            Console.ReadKey();
        }
    }
}

Теперь я знаю, что метод ResetAbort должен препятствовать тому, чтобы ThreadAbortException продолжал перебрасывать себя, даже когда инструкция catch ловит его, но мой вопрос таков:

Если кто-то может использовать метод ResetAbort, то какая точка исключения специально перебрасывает себя?

пользователь может просто сделать

    catch (ThreadAbortException ex)
    {
        Console.WriteLine("Abort!");
        throw ex;
    }

Ответ 1

Точка регенерации ThreadAbortException состоит в том, чтобы убедиться, что поток завершается, если пользователь явно не называет ResetAbort.

Позвольте мне объяснить:

try
{
    // ... Thread abort happens somewhere in here
}
catch (Exception ex)
{
    _log.Error(ex);
}

Здесь у вас есть типичный пример кода, который не гарантирует, что исключение распространяется изнутри блока try. Я знаю, что ловить Exception - это плохая практика, но такой код существует, тем не менее.

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

try
{
    // ... Thread abort happens somewhere in here
}
catch (ThreadAbortException)
{
    throw; // No, you'll NEVER see code like this in real life
}
catch (Exception ex)
{
    _log.Error(ex);
}

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

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

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

Ответ 2

Thread.ResetAbort() не предназначен для общего использования. Это может привести к нежелательному поведению, если вы не понимаете, почему произошло прерывание беременности. Из-за этого и, возможно, для стабилизации ASP.NET в средах с общим хостингом, разрешение SecurityPermissionFlag.ControlThread требуется для вызова Thread.ResetAbort()

Ссылка MSDN

Ответ 3

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

Кроме того, ResetAbort имеет требование безопасности и не может быть вызван каким-либо кодом.

Ответ 4

Если вы не нацелены на создание приложения, которое демонстрирует когнитивные искажения или раздвоение поведения личности или какой-то самоперерабатывающий/мутирующий код.. (что может быть очень круто - я знаю..:))

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

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

Рассмотрим следующий пример:

Предположим, вы разрабатываете многопоточное приложение, которое включает информацию о базе данных.

И.. Пусть говорят, что ваш пользователь дал указание сделать какое-то пакетное задание, которое связано с обработкой и сохранением данных в вашей БД.
И.. эта задача - скажем, занимает 30 секунд для завершения.
Итак, вы открываете для него поток и управляете этим в фоновом режиме. (назовите его "A" )

В то же время.. Ваш пользователь нажал кнопку выхода из программы - и он сделал это, пока поток "A" все еще находится в процессе выполнения.

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

Так что теперь делать? - Существуют три дисциплины:

  • Выйти немедленно - пользователь свят! и если это то, что он хотел - Кто я такой, чтобы расспрашивать его.

  • Завершить задачу из потока "A" и только потом выйти.

  • Откат по последним инструкциям потока "А" и только после этого выход.

В варианте 1 целостность базы данных может быть скомпрометирована из-за действия пользователя.
В вариантах 2 и 3 вы отдавали приоритет логическому потоку за счет реагирования на ваш запрос пользователя.

Итак, если вы действительно скромны, вы, вероятно, выберете вариант 2 или вариант 3.

Но предполагая, что ваш метод выхода используется Thread.Abort(),
Вам нужно будет использовать Thread.ResetAbort() для "переопределения" и завершения вашей цели.

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

Обычно хорошая практика позволяет методу выхода знать, что происходит.
Обычно хорошей практике не нужно отменять нить (она сама открыта).

Заключение,
да - хорошо знать, что это есть... и no - это плохо использовать.
p >

Если вы не используете Thread.Abort() в потоке, который вы не открыли (возможно - сложно..),
Или ожидая, что вас прервут извне, и поэтому понадобится Thread.ResetAbort().

В противном случае - нет необходимости использовать его.

Ответ 5

Потому что прерывание потока не обязательно означает, что будет выбрано исключение. Для процедуры прерывания блок catch (ThreadAbortException) - это еще одна критическая область кода. Это дает нам поточный безопасный и удобный способ обнаружения, если текущий поток прерывается (а может быть, и с некоторым состоянием), если мы хотим сделать что-то особенное. Помимо этого, он похож на любой другой критический регион (например, блок finally), где он завершит поток после его выполнения.

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

Приведенный ниже код иллюстрирует этот факт, восстанавливая прерванный поток без каких-либо исключений:

var inFinally = new ManualResetEvent(false);
var abortCalled = new ManualResetEvent(false);

var t = new Thread(_ =>
{
    Console.WriteLine("Thread started..");
    try
    {
    }
    finally
    {
        inFinally.Set();
        abortCalled.WaitOne();
        Console.WriteLine(" ThreadState (before): " + Thread.CurrentThread.ThreadState);

        // This isn't thread safe, and ugly?
        if ((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested) != 0)
        {
            Thread.ResetAbort();
        }

        Console.WriteLine(" ThreadState (after): " + Thread.CurrentThread.ThreadState);
    }
    Console.WriteLine("Executed because we called Thread.ResetAbort()");
});

t.Start();

inFinally.WaitOne();

// Call from another thread because Abort()
// blocks while in finally block
ThreadPool.QueueUserWorkItem(_ => t.Abort());

while ((t.ThreadState & ThreadState.AbortRequested) == 0)
{
    Thread.Sleep(1);
}

abortCalled.Set();

Console.ReadLine();

//  Output:
//--------------------------------------------------
// Thread started..
//  ThreadState (before): AbortRequested
//  ThreadState (after): Running
// Executed because we called Thread.ResetAbort()

Теперь, я должен быть честным: я не совсем уверен, как можно использовать эту функцию и создать что-то полезное. Но похоже, что Thread.Abort API был (вероятно, еще не я), который использовался для облегчения использования потоков и повторного использования AppDomain в таких инфраструктурах, как ASP.NET.

В одном из записей в блоге Joe Duffy управляемый код и асинхронное упрощение исключений, он говорит о ResetAbort и Abort API:

Некоторая инфраструктура инфраструктуры, особенно ASP.NET, даже прерывает отдельные потоки обычно без выгрузки домена. Они поддерживают блокировку ThreadAbortExceptions, вызывают ResetAbort в потоке и повторно использовать его или вернуть его в CLR ThreadPool.

Я могу представить, что он может использоваться в рамках, чтобы повторно использовать управляемые потоки, уменьшая накладные расходы. Тем не менее, проблемы (дизайн синхронизации плохой нити, неправильная обработка исключений, мертвые блокировки и т.д.), Введенные в код пользователя, благодаря этому легко понятному API, заставили Abort и ResetAbort вызвать более неприятные, чем полезные.