Приостановка и прерывание потоков
Наиболее распространенными способами синхронизации действий потоков являются блокировка и освобождение потоков или блокировка объектов или областей кода. Подробнее об этих механизмах фиксации и блокировки см. в разделе Обзор примитивов синхронизации.
Также можно организовать перевод потоков в спящий режим. Если потоки заблокированы или находятся в спящем режиме, можно использовать ThreadInterruptedException для вывода потоков из состояния ожидания.
Метод Thread.Sleep
Вызов метода Thread.Sleep приводит к немедленной блокировке текущего потока на определенное количество миллисекунд, переданное этому методу, вследствие чего остаток среза времени передается другому потоку. По истечении этого интервала времени спящий поток возобновляет выполнение.
Поток не может вызвать метод Thread.Sleep для другого потока. Статический метод Thread.Sleep всегда переводит текущий поток в спящий режим.
Вызов Thread.Sleep со значением Timeout.Infinite приводит к тому, что поток переходит в спящий режим до тех пор, пока он не будет прерван другим потоком, который вызывает Thread.Interrupt метод в спящем потоке, или пока он не будет завершен вызовом метода Thread.Abort . В следующем примере показаны оба метода прерывания спящего потока.
using System; using System.Threading; public class Example < public static void Main() < // Interrupt a sleeping thread. var sleepingThread = new Thread(Example.SleepIndefinitely); sleepingThread.Name = "Sleeping"; sleepingThread.Start(); Thread.Sleep(2000); sleepingThread.Interrupt(); Thread.Sleep(1000); sleepingThread = new Thread(Example.SleepIndefinitely); sleepingThread.Name = "Sleeping2"; sleepingThread.Start(); Thread.Sleep(2000); sleepingThread.Abort(); >private static void SleepIndefinitely() < Console.WriteLine("Thread '' about to sleep indefinitely.", Thread.CurrentThread.Name); try < Thread.Sleep(Timeout.Infinite); >catch (ThreadInterruptedException) < Console.WriteLine("Thread '' awoken.", Thread.CurrentThread.Name); > catch (ThreadAbortException) < Console.WriteLine("Thread '' aborted.", Thread.CurrentThread.Name); > finally < Console.WriteLine("Thread '' executing finally block.", Thread.CurrentThread.Name); > Console.WriteLine("Thread ' finishing normal execution.", Thread.CurrentThread.Name); Console.WriteLine(); > > // The example displays the following output: // Thread 'Sleeping' about to sleep indefinitely. // Thread 'Sleeping' awoken. // Thread 'Sleeping' executing finally block. // Thread 'Sleeping finishing normal execution. // // Thread 'Sleeping2' about to sleep indefinitely. // Thread 'Sleeping2' aborted. // Thread 'Sleeping2' executing finally block.
Imports System.Threading Module Example Public Sub Main() ' Interrupt a sleeping thread. Dim sleepingThread = New Thread(AddressOf Example.SleepIndefinitely) sleepingThread.Name = "Sleeping" sleepingThread.Start() Thread.Sleep(2000) sleepingThread.Interrupt() Thread.Sleep(1000) sleepingThread = New Thread(AddressOf Example.SleepIndefinitely) sleepingThread.Name = "Sleeping2" sleepingThread.Start() Thread.Sleep(2000) sleepingThread.Abort() End Sub Private Sub SleepIndefinitely() Console.WriteLine("Thread '' about to sleep indefinitely.", Thread.CurrentThread.Name) Try Thread.Sleep(Timeout.Infinite) Catch ex As ThreadInterruptedException Console.WriteLine("Thread '' awoken.", Thread.CurrentThread.Name) Catch ex As ThreadAbortException Console.WriteLine("Thread '' aborted.", Thread.CurrentThread.Name) Finally Console.WriteLine("Thread '' executing finally block.", Thread.CurrentThread.Name) End Try Console.WriteLine("Thread ' finishing normal execution.", Thread.CurrentThread.Name) Console.WriteLine() End Sub End Module ' The example displays the following output: ' Thread 'Sleeping' about to sleep indefinitely. ' Thread 'Sleeping' awoken. ' Thread 'Sleeping' executing finally block. ' Thread 'Sleeping finishing normal execution. ' ' Thread 'Sleeping2' about to sleep indefinitely. ' Thread 'Sleeping2' aborted. ' Thread 'Sleeping2' executing finally block.
Прерывание потоков
Ожидающий поток можно прервать, вызвав метод Thread.Interrupt для заблокированного потока. Это действие создает исключение ThreadInterruptedException, которое выводит поток из вызова блокировки. Поток должен перехватить исключение ThreadInterruptedException и выполнить соответствующие действия для продолжения работы. Если поток пропускает исключение, среда выполнения перехватывает его и останавливает поток.
Если целевой поток не заблокирован при вызове метода Thread.Interrupt, поток не прерывается до блокировки. Если поток никогда не блокируется, он может завершиться, не будучи прерванным.
Если ожидание является управляемым, методы Thread.Interrupt и Thread.Abort незамедлительно выводят поток из спящего режима. Если ожидание является неуправляемым (как, например, вызов неуправляемого кода функции Win32 WaitForSingleObject), то методы Thread.Interrupt и Thread.Abort не могут управлять потоком, пока он не вернется в управляемый код или не вызовет управляемый код. В управляемом коде это поведение выглядит следующим образом:
- Thread.Interrupt выводит поток из состояния ожидания, в котором он может находиться, и приводит к созданию исключения ThreadInterruptedException в целевом потоке.
- Только платформа .NET Framework: Thread.Abort выводит поток из любого ожидания, в который он может находиться, и вызывает ThreadAbortException исключение в потоке. Дополнительные сведения см. в разделе Уничтожение потоков.
См. также раздел
- Thread
- ThreadInterruptedException
- ThreadAbortException
- Работа с потоками
- Использование потоков и работа с потоками
- Обзор примитивов синхронизации
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
The .NET documentation is open source. Provide feedback here.
Обратная связь
Отправить и просмотреть отзыв по
Как остановить поток
Примеры потоков ранее представляли поток как последовательный набор операций. После выполнения последней операции завершался и поток. Однако нередко имеет место и другая организация потока в виде бесконечного цикла. Например, поток сервера в бесконечном цикле прослушивает определенный порт на предмет получения данных. И в этом случае мы также можем предусмотреть механизм завершения потока.
Завершение потока
Распространенный способ завершения потока представляет опрос логической переменной. И если она равна, например, false, то поток завершает бесконечный цикл и заканчивает свое выполнение.
Определим следующий класс потока:
class MyThread implements Runnable < private boolean isActive; void disable()< isActive=false; >MyThread() < isActive = true; >public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(isActive)< System.out.println("Loop " + counter++); try< Thread.sleep(400); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >> System.out.printf("%s finished. \n", Thread.currentThread().getName()); > >
Переменная isActive указывает на активность потока. С помощью метода disable() мы можем сбросить состояние этой переменной.
Теперь используем этот класс:
public static void main(String[] args) < System.out.println("Main thread started. "); MyThread myThread = new MyThread(); new Thread(myThread,"MyThread").start(); try< Thread.sleep(1100); myThread.disable(); Thread.sleep(1000); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.println("Main thread finished. "); >
Итак, вначале запускается дочерний поток: new Thread(myThread,»MyThread»).start() . Затем на 1100 миллисекунд останавливаем Main thread и потом вызываем метод myThread.disable() , который переключает в потоке флаг isActive. И дочерний поток завершается.
Метод interrupt
Еще один способ вызова завершения или прерывания потока представляет метод interrupt() . Вызов этого метода устанавливает у потока статус, что он прерван. Сам метод возвращает true, если поток может быть прерван, в ином случае возвращается false.
При этом сам вызов этого метода НЕ завершает поток, он только устанавливает статус: в частности, метод isInterrupted() класса Thread будет возвращать значение true. Мы можем проверить значение возвращаемое данным методом и прозвести некоторые действия. Например:
class JThread extends Thread < JThread(String name)< super(name); >public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(!isInterrupted())< System.out.println("Loop " + counter++); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); > > public class Program < public static void main(String[] args) < System.out.println("Main thread started. "); JThread t = new JThread("JThread"); t.start(); try< Thread.sleep(150); t.interrupt(); Thread.sleep(150); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.println("Main thread finished. "); > >
В классе, который унаследован от Thread, мы можем получить статус текущего потока с помощью метода isInterrupted() . И пока этот метод возвращает false, мы можем выполнять цикл. А после того, как будет вызван метод interrupt, isInterrupted() возвратит true, и соответственно произойдет выход из цикла.
Возможный консольный вывод:
Main thread started. JThread started. Loop 1 Loop 2 Loop 3 Loop 4 JThread finished. Main thread finished.
Если основная функциональность заключена в классе, который реализует интерфейс Runnable, то там можно проверять статус потока с помощью метода Thread.currentThread().isInterrupted() :
class MyThread implements Runnable < public void run()< System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(!Thread.currentThread().isInterrupted())< System.out.println("Loop " + counter++); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); > > public class Program < public static void main(String[] args) < System.out.println("Main thread started. "); MyThread myThread = new MyThread(); Thread t = new Thread(myThread,"MyThread"); t.start(); try< Thread.sleep(150); t.interrupt(); Thread.sleep(150); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.println("Main thread finished. "); > >
Однако при получении статуса потока с помощью метода isInterrupted() следует учитывать, что если мы обрабатываем в цикле исключение InterruptedException в блоке catch, то при перехвате исключения статус потока автоматически сбрасывается, и после этого isInterrupted будет возвращать false.
Например, добавим в цикл потока задержку с помощью метода sleep:
public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов while(!isInterrupted())< System.out.println("Loop " + counter++); try< Thread.sleep(100); >catch(InterruptedException e) < System.out.println(getName() + " has been interrupted"); System.out.println(isInterrupted()); // false interrupt(); // повторно сбрасываем состояние >> System.out.printf("%s finished. \n", Thread.currentThread().getName()); >
Когда поток вызовет метод interrupt, метод sleep сгенерирует исключение InterruptedException, и управление перейдет к блоку catch. Но если мы проверим статус потока, то увидим, что метод isInterrupted возвращает false. Как вариант, в этом случае мы можем повторно прервать текущий поток, опять же вызвав метод interrupt(). Тогда при новой итерации цикла while метода isInterrupted возвратит true, и поизойдет выход из цикла.
Либо мы можем сразу же в блоке catch выйти из цикла с помощью break:
while(!isInterrupted()) < System.out.println("Loop " + counter++); try< Thread.sleep(100); >catch(InterruptedException e) < System.out.println(getName() + " has been interrupted"); break; // выход из цикла >>
Если бесконечный цикл помещен в конструкцию try. catch, то достаточно обработать InterruptedException:
public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); int counter=1; // счетчик циклов try< while(!isInterrupted())< System.out.println("Loop " + counter++); Thread.sleep(100); >> catch(InterruptedException e) < System.out.println(getName() + " has been interrupted"); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); >
Как остановить поток?
В Java поток представлен классом Thread . В нём есть метод stop() , но пользоваться им нельзя, метод помечен как deprecated. Такая жесткая остановка моментально возвращает все захваченные потоком мониторы, и защищенные ими данные могут оказаться в неконсистентном состоянии.
Разработчики рекомендуют вместо этого использовать флаг, который будет показывать о намерении остановить поток. Флаг выставляется извне потока, а внутри проверяется в подходящий момент. Если нужно остановиться, поток просто выходит из метода run() . В качестве такого флага подойдет переменная типа AtomicBoolean .
Когда в потоке используются блокирующие операции, обычно для определенного типа операции существует свой способ её прервать. Например, можно закрыть сокет, на котором поток ожидает. Для большинства блокирующих операций сработает метод Thread.interrupt() . С его помощью можно прервать Object.wait() и операции из NIO.
Останется только правильно обработать такое прерывание. Прерванный wait() выбросит InterruptedException , Selector.select() вернет результат. Чтобы отличить осознанное прерывание с целью завершить тред от какого-либо другого, его обработку всё ещё необходимо снабдить проверкой флага.
Прерывание потока
Иногда поток полезно прервать до его нормального завершения. Например, отладчику может понадобиться прервать вышедший из-под контроля поток. После прерывания поток удаляется из системы и не может быть начат снова. Для прерывания потока до его нормального завершения служит метод Abort(). Ниже приведена простейшая форма этого метода:
public void Abort()
Метод Abort() создает необходимые условия для генерирования исключения ThreadAbortException в том потоке, для которого он был вызван. Это исключение приводит к прерыванию потока и может быть перехвачено и в коде-программы, но в этом случае оно автоматически генерируется еще раз, чтобы остановить поток. Метод Abort() не всегда способен остановить поток немедленно, поэтому если поток требуется остановить перед тем, как продолжить выполнение программы, то после метода Abort() следует сразу же вызвать метод Join().
Кроме того, в самых редких случаях методу Abort() вообще не удается остановить поток. Это происходит, например, в том случае, если кодовый блок finally входит в бесконечный цикл. В приведенном ниже примере программы демонстрируется применение метода Abort() для прерывания потока:
using System; using System.Threading; namespace ConsoleApplication1 < class MyThread < int num; public Thread Thrd; public MyThread(string name) < Thrd = new Thread(this.Run); Thrd.Name = name; Thrd.Start(); >// Точка входа в поток void Run() < Console.WriteLine(Thrd.Name + " начат"); for (int i = 1; i > Console.WriteLine(Thrd.Name + " завершен"); > > class Program < static void Main() < MyThread mt1 = new MyThread("Мой поток"); Thread.Sleep(1000); Console.WriteLine("Прерывание потока"); mt1.Thrd.Abort(); // Ожидание прерывания mt1.Thrd.Join(); Console.WriteLine("Основной поток прерван"); Console.ReadLine(); >> >

Метод Abort() не следует применять в качестве обычного средства прерывания потока, поскольку он предназначен для особых случаев. Обычно поток должен завершаться естественным образом, чтобы произошел возврат из метода, выполняющего роль точки входа в него.
В некоторых случаях оказывается полезной другая форма метода Abort (), приведенная ниже в общем виде:
public void Abort(object stateInfo)
где stateInfo обозначает любую информацию, которую требуется передать потоку, когда он останавливается. Эта информация доступна посредством свойства ExceptionState из класса исключения ThreadAbortException. Подобным образом потоку можно передать код завершения.
Запрос на преждевременное прерывание может быть переопределен в самом потоке. Для этого необходимо сначала перехватить в потоке исключение ThreadAbortException, а затем вызвать метод ResetAbort(). Благодаря этому исключается повторное генерирование исключения по завершении обработчика исключения, прерывающего данный поток. Ниже приведена форма объявления метода ResetAbort():
public static void ResetAbort()
Вызов метода ResetAbort() может завершиться неудачно, если в потоке отсутствует надлежащий режим надежной отмены преждевременного прерывания потока. Давайте модифицируем вышеуказанный пример:
using System; using System.Threading; namespace ConsoleApplication1 < class MyThread < public Thread Thrd; public MyThread(string name) < Thrd = new Thread(this.Run); Thrd.Name = name; Thrd.Start(); >// Точка входа в поток void Run() < Console.WriteLine(Thrd.Name + " начат"); for (int i = 1; i > catch (ThreadAbortException exc) < if ((int)exc.ExceptionState == 0) < Console.WriteLine("Прерывание отменено! Код завершения: " + exc.ExceptionState); Thread.ResetAbort(); >else Console.WriteLine("Поток прерван, код завершения " + exc.ExceptionState); > > Console.WriteLine(Thrd.Name + " завершен"); > > class Program < static void Main() < MyThread mt1 = new MyThread("Мой поток"); Thread.Sleep(1000); Console.WriteLine("Прерывание потока"); mt1.Thrd.Abort(); // Ожидание прерывания mt1.Thrd.Join(); Console.WriteLine("Основной поток прерван"); Console.ReadLine(); >> >
