Уничтожение потока .NET

Я создал поток, выполняющий определенный метод. Но иногда я хотел бы убить поток, даже если он все еще работает. Как я могу это сделать? Я попробовал Thread.Abort(), но он показывает окно сообщения с надписью «Поток прерван». Что я должен делать?


person Jorge Branco    schedule 27.06.2009    source источник
comment
Вы читали раздел комментариев в MSDN? НИКОГДА НЕ ИСПОЛЬЗУЙТЕ ПРЕРЫВАНИЕ. ОНА ПРЕДНАЗНАЧЕНА КАК ПОСЛЕДНЯЯ СРЕДСТВО. ЕСЛИ ВЫ ИСПОЛЬЗУЕТЕ THREAD.ABORT, НЕБО МОЖЕТ Упасть, И КОТЕНОК БУДЕТ УБИТ.   -  person J-16 SDiZ    schedule 27.06.2009
comment
Котенок? Я думал, что это курица :) Но твоя аватарка объясняет твой комментарий!   -  person SO User    schedule 27.06.2009
comment
Как уже говорили другие, делайте все возможное, чтобы никогда этого не делать; вы должны стараться никогда не создавать поток, который делает что-то, что вы не можете контролировать. Если вам нужно убить работающий код, который вы не можете контролировать, безопаснее всего запустить код в другом ПРОЦЕССЕ, а не в другом ПОТОКЕ. Гораздо безопаснее остановить процесс, чем поток.   -  person Eric Lippert    schedule 27.06.2009


Ответы (8)


Не звонить Thread.Abort()!

Thread.Abort опасно. Вместо этого вы должны сотрудничать с потоком, чтобы его можно было мирно закрыть. Поток должен быть спроектирован таким образом, чтобы ему можно было приказать убить себя, например, с помощью логического флага keepGoing, который вы устанавливаете в false, когда хотите, чтобы поток остановился. Тогда поток будет иметь что-то вроде

while (keepGoing)
{
    /* Do work. */
}

Если поток может заблокироваться в Sleep или Wait, вы можете вывести его из этих функций, вызвав Thread.Interrupt(). Затем поток должен быть готов к обработке ThreadInterruptedException:

try
{
    while (keepGoing)
    {
        /* Do work. */
    }
}
catch (ThreadInterruptedException exception)
{
    /* Clean up. */
}
person John Kugelman    schedule 27.06.2009
comment
Использование обработки исключений для управления потоком программы — ужасная идея. Вместо этого используйте WaitHandle. - person Mark Seemann; 27.06.2009
comment
Я не думаю, что это так ужасно. Если ваш цикл недостаточно плотный и может потребоваться 30 секунд обработки, прежде чем он, наконец, узнает, что должен выйти, исключение — идеальный способ его остановить. - person Jimbo; 29.04.2010
comment
Марк, а что, если ваш поток блокируется для ввода, и вам нужно как-то остановить его? Он может блокироваться на неопределенный срок, поэтому его прерывание и обработка исключения — это единственный способ заставить его выйти. - person Kiril; 03.09.2010
comment
@Lirik Тогда вам следует использовать неблокирующие версии или асинхронные вызовы с возможностью отмены. Подумайте select() в Unix. - person Jonathon Reinhart; 12.11.2013
comment
Так как же получить поток из SqlCommand.ExecuteNonQuery()? Не говорите, что вызовите Async() здесь, вы только что переместили проблему. - person Joshua; 17.07.2021

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

volatile bool shutdown = false;

void RunThread()
{
   while (!shutdown)
   {
      ...
   }
}

void StopThread()
{
   shutdown = true;
}

Это позволяет вашему потоку аккуратно завершить то, что он делал, оставив ваше приложение в заведомо хорошем состоянии.

person Jon B    schedule 27.06.2009

Самый правильный и потокобезопасный способ — использовать WaitHandle для подачи сигнала потоку, когда он должен остановиться. Я в основном использую ManualResetEvent.

В вашем потоке вы можете иметь:

private void RunThread()
{
    while(!this.flag.WaitOne(TimeSpan.FromMilliseconds(100)))
    {
        // ...
    }
}

где this.flag — экземпляр ManualResetEvent. Это означает, что вы можете вызвать this.flag.Set() извне потока, чтобы остановить цикл.

Метод WaitOne вернет true только тогда, когда установлен флаг. В противном случае он истечет по истечении указанного времени ожидания (в примере 100 мс), и поток снова пройдет через цикл.

person Mark Seemann    schedule 27.06.2009
comment
Недостатком вашей реализации является то, что в каждом цикле вы будете ждать события, ничего не делая. Я бы предпочел наоборот. Наличие флага, указывающего на завершение работы и сигнализирующего о завершении выполнения протектора. while (!WillTerminate) {..} HasTerminate.Set(); - person mathk; 30.07.2012
comment
будет ли это работать в событии DoWork BackgroundWorker? - person Ehsan Sajjad; 14.04.2016

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

  • Используйте Thread.Interrupt, чтобы ткнуть его, если он заблокирован.
  • Опрос флаговой переменной.
  • Используйте класс WaitHandle для отправки сигнала.

Мне не нужно перефразировать, как можно использовать каждый метод, поскольку я уже сделал это в этот ответ.

person Brian Gideon    schedule 20.09.2010
comment
+1. Можете ли вы предоставить простой пример уничтожения/остановки потока с помощью дескриптора ожидания? - person Royi Namir; 23.07.2012

Прерывание потока — очень плохая идея, поскольку вы не можете определить, что делал поток в момент прерывания.

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

person John Saunders    schedule 27.06.2009

Я согласен с Джоном Б.

volatile bool shutdown = false;

void RunThread()
{

try
{
    while (!shutdown)
    {
        /* Do work. */
    }
}
catch (ThreadAbortException exception)
{
    /* Clean up. */
}
}

void StopThread()
{
   shutdown = true;
}
person XenKid    schedule 19.05.2012

Есть также примеры уничтожения потоков в моем классе WebServer...

https://net7ntcip.codeplex.com/SourceControl/changeset/view/89621#1752948

Я бы сказал, что Abort в порядке, просто поймите, каковы последствия ... пока вы указываете состояние до того, как длительная задача Abort будет работать, но требуются флаги, такие как (ShouldStop или ActionBranch и т. д.)

Посмотрите на примеры!

person Jay    schedule 19.05.2012

Редактировать:

  1. Я создал небольшой класс для этого

  2. Заметил, что это не работает с async/await

  3. Опубликовал обновление в классе, затем Теодор Зулиас (спасибо :)) сообщил мне, что эта идея ошибочна.

  4. Теперь я публикую исходный класс (не работает с async/await!)

    var t = new StopAbleThread(isShutDown =>
     {
       Console.WriteLine("a");
       while (!isShutDown())
       {
         Console.WriteLine("b");
         Thread.Sleep(TimeSpan.FromMinutes(10));
       }
     }  )   
    
    { IsBackground = true };
       t.Start( );
    

Остановить ветку так:

t.Stop();

Класс:

public class StopAbleThread
{
  public bool IsBackground
  {
    set => _thread.IsBackground = value;
  }

  private readonly Thread _thread;
  private volatile bool _shutdown;

  public delegate bool IsShutDown( );
  public delegate void CodeToRun( IsShutDown isShutDown );


  public StopAbleThread( CodeToRun codeToRun )
  {
    _thread = new Thread( ( ) =>
    {
      try
      {
        codeToRun( _IsShutDown );
      }
      catch( ThreadInterruptedException )
      {
        //ignore
      }
    } );
  }

  private bool _IsShutDown( )
  {
    return _shutdown;
  }

  public void Start( )
  {
    _thread.Start( );
  }

  public void Stop( )
  {
    _shutdown = true;
    _thread.Interrupt( );
  }
}
person Rowi123    schedule 16.07.2021
comment
Конструктор Thread не понимает асинхронные делегаты. Вы можете прочитать об этом здесь или здесь. В результате _thread.Interrupt() прервет уже завершенный поток, так что на практике он ничего не сделает. - person Theodor Zoulias; 16.07.2021
comment
Спасибо за комментарий. Приведенный выше код работает, как и ожидалось, для моего варианта использования. Вызов прерывания пробуждает поток во время сна. - person Rowi123; 16.07.2021
comment
первая версия вашего ответа была в порядке, потому что весь код был синхронным. Добавив async/await, вы создали ошибочную версию, которая не будет работать последовательно. Нет никакой гарантии, что код после точки await будет продолжать выполняться в том же потоке, что и до ожидания. В вашем примере точка await Task.Delay(1) — это место, где _thread завершится, а последующая Thread.Sleep(FromMinutes(10)) усыпит поток ThreadPool вместо _thread. И тогда _thread.Interrupt(); ничего не сделает. - person Theodor Zoulias; 17.07.2021
comment
Если вы мне не верите, войдите в Console Thread.CurrentThread.ManagedThreadId до и после await и посмотрите, совпадают ли они или нет. - person Theodor Zoulias; 17.07.2021
comment
Хорошо, еще раз спасибо. Думаю, я должен опубликовать первое решение? И мне действительно нужно решение с ожиданием. Вы знаете лучший способ? - person Rowi123; 17.07.2021
comment
Да, я бы предложил откатиться до версии 1. Что касается того, как завершить асинхронную операцию без взаимодействия, я, честно говоря, не знаю! - person Theodor Zoulias; 17.07.2021