Использование Thread.Abort () и обработка ThreadAbortException в безопасной практике .NET? - PullRequest
7 голосов
/ 17 июня 2011

Мне нужно разработать многопоточную рабочую роль Azure в C # - создавать несколько потоков, направлять запросы к ним, каждый запрос может потребовать очень много времени для обработки (не мой код - я вызову объект COM для выполнения реальной работы).).

После отключения роли нужно аккуратно прекратить обработку.Как я могу это сделать?Похоже, если я просто вызываю Thread.Abort(), то ThreadAbortException выбрасывается в поток, и поток может даже использовать try-catch-finally (или using) для очистки ресурсов.Это выглядит довольно надежно.

Что меня беспокоит, так это то, что мой опыт в основном C ++, и невозможно изящно прервать поток в неуправляемом приложении - он просто остановится без дальнейшей обработки, и это может привести к тому, что данные будут в несогласованном состоянии,Так что я немного параноидален по поводу того, происходит ли что-нибудь подобное в случае, если я вызываю Thread.Abort() для занятого потока.

Безопасно ли использовать Thread.Abort() вместе с обработкой ThreadAbortException?О чем мне следует знать, если я это сделаю?

Ответы [ 7 ]

7 голосов
/ 17 июня 2011

Использует ли Thread.Abort () и обрабатывает ли исключение ThreadAbortException в безопасной практике .NET?

TL; версия DR: нет, не.

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

Исключение внеполосного 1 , которое выдается в потоке в таких инвариантах-не-истина, т.е. недействительно, состояние, где начинаются проблемы. Эти проблемы включают, но не ограничиваются, взаимно несовместимые значения полей и свойств (структуры данных в недопустимом состоянии), блокировки не завершены, а события, представляющие «произошли изменения», не сработали.

Во многих случаях с этим можно разобраться в коде очистки (например, блок finally), , но , а затем рассмотрите, что произойдет, если во время этой очистки возникнет исключение вне диапазона. код? Это приводит к очистке кода для очистки кода. Но тогда этот код самочувствителен, так что вам нужен код очистки для кода очистки кода очистки & hellip; это никогда не кончается!

Существуют решения, но их нелегко спроектировать (и, как правило, они влияют на весь ваш дизайн), и еще сложнее протестировать - как воссоздать все дела (например, комбинаторный взрыв). Два возможных маршрута:

  1. Работа с копиями состояния, обновление копий, а затем атомная замена текущего состояния на новое. Если есть внеполосное исключение, то сохраняется исходное состояние (и финализаторы могут очистить временное состояние).

    Это похоже на функцию транзакций базы данных (хотя СУБД работают с блокировками и файлами журналов транзакций).

    Это также похоже на подходы к достижению «гарантии строгих исключений», разработанные в сообществе C ++ в ответ на бумажный опрос, могут ли исключения быть когда-либо безопасными (C ++, конечно, не имеет GC / финализатор очередь для очистки выброшенных объектов). См. Herb Sutters «Гуру недели № 8: ВЫЗОВ ВЫПУСК: Исключительная безопасность» для решения.

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

  2. Посмотрите на «Области ограниченного выполнения» , но не ограничения на то, что вы можете сделать в этих случаях. (В журнале MSDN была вступительная статья (введение в предмет, а не вступительный уровень), начиная с бета-версии .NET 2 2 ).

На практике, если вам нужно сделать это, использование подхода № 2 для управления изменением состояния под № 1, вероятно, является лучшим подходом, но получить его правильно, а затем проверить, что это правильно (и правильность поддерживается), жесткий .

Резюме: это немного похоже на оптимизацию: правило 1: не делайте этого; Правило 2 (только для экспертов): не делайте этого, если у вас нет другого выбора.


1 A ThreadAbortException не единственное такое исключение.

2 Так что детали, возможно, изменились.

5 голосов
/ 17 июня 2011

Один пример, когда проблематично прервать поток.

using(var disposable=new ClassThatShouldBeDisposed())
{
   ...
}

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

Детерминированная утилизация и прерывание нити плохо работают вместе. Единственный известный мне способ безопасного удаления при использовании прерывания потока - поместить весь критический код в предложение finally.

try
{//Empty try block
}
finally
{
  //put all your code in the finally clause to fool thread abortion
  using(var disposable=new ClassThatShouldBeDisposed())
  {
  ...
  }
}

Это работает, потому что прерывание потока позволяет выполнить код finally. Конечно, это означает, что прерывание потока просто не будет работать, пока код не покинет блок finally.

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

ClassThatShouldBeDisposed disposable=null;
try
{
  try{}finally{disposable=new ClassThatShouldBeDisposed();}
  //Do your work here
}
finally
{
  if(disposable!=null)
    disposable.Dispose();
}

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

4 голосов
/ 17 июня 2011

Thread.Abort - это небезопасный способ уничтожения нити.

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

Рекомендуется использовать оболочки, поддерживающие отмену работы, такие как класс Task или использование volatile bool. Вместо Thread.Abort рассмотрите возможность использования Thread.Join, которая будет блокировать вызывающий поток, пока рабочий поток не будет удален.

Некоторые ссылки:
- Как остановить поток в .NET (и почему Thread.Abort is Evil)
- Защита от управляемого кода и асинхронных исключений
- Опасности Thread.Abort

4 голосов
/ 17 июня 2011

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

Большая часть кода написана с предположением, что некоторые действия, например int i = 0;, никогда не вызывают исключение, поэтому обработка критического исключения применяется только к коду, который на самом деле может вызвать исключение само по себе. Когда вы прерываете поток, исключение может появиться в коде, который не подготовлен для его обработки.

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

1 голос
/ 17 июня 2011

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

Может быть лучше использовать событие:

class ThreadManager
{
    private Thread thread = new Thread(new ThreadStart(CallCOMMethod));
    private AutoResetEvent endThread = new AutoResetEvent(false);

    public ThreadManager()
    {
        thread.Start();
    }

    public StopThread()
    {
        endThread.Set();
    }

    private void CallCOMMethod()
    {
        while (!endThread.WaitOne())
        {
           // Call COM method
        }
    } 
}

Поскольку COM-метод долго выполняется, вам может понадобиться просто «откусить пулю» и подождать, пока он завершит свою текущую итерацию. Информация вычисляется во время текущей итерации значения для пользователя?

Если нет, то один из вариантов может быть:

  • Пусть ThreadManager запускается в отдельном потоке от пользовательского интерфейса, который относительно часто проверяет уведомление пользователя об остановке.
  • Когда пользователь запрашивает остановку длительной операции, поток пользовательского интерфейса может немедленно вернуться к пользователю.
  • ThreadManager ожидает, пока длительная операция COM завершит текущую итерацию, а затем отбрасывает результаты.
0 голосов
/ 17 июня 2011

Пожалуйста, получите простое представление о вашем требовании, проверьте свойство theadalive, а затем прервите ваш поток ........................... ..................................

        ThreadStart th = new ThreadStart(CheckValue);
        System.Threading.Thread th1 = new Thread(th);

       if(taskStatusComleted)
      { 
        if (th1.IsAlive)
        {
            th1.Abort();
        }
      }

private void CheckValue () { // мой метод .... }

0 голосов
/ 17 июня 2011

Лучше всего просто возвращать метод потока:

void Run()   // thread entry function
{
   while(true)
   {
     if(somecondition)  // check for a terminating condition - usually "have I been disposed?"
         break;

     if(workExists)
        doWork();

     Thread.Sleep(interval);
   }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...