![]() |
![]() |
![]() |
Метод Join
Иногда
программный поток требуется
приостановить до момента
завершения другого потока.
Допустим, вы хотите приостановить
поток 1 до тех пор, пока поток 2 не
завершит свои вычисления. Для этого
из потока 1 вызывается метод Join
для потока 2. Иначе говоря, команда
thread2.Join()
приостанавливает
текущий поток и ожидает завершения
потока 2. Поток 1 переходит в заблокированное
состояние.
Если присоединить поток 1 к потоку 2 методом Join, операционная система автоматически запустит поток 1 после завершения потока 2. Учтите, что процесс запуска является недетерминированным: нельзя точно сказать, через какой промежуток времени после завершения потока 2 заработает поток 1. Существует и другая версия Join, которая возвращает логическую величину:
thread2.Join(Integer)
Этот метод либо
ожидает завершения потока 2, либо
разблокирует поток 1 после
истечения заданного интервала
времени, вследствие чего
планировщик операционной системы
снова будет выделять потоку
процессорное время. Метод
возвращает True, если поток 2
завершается до истечения заданного
интервала тайм-аута, и False в
противном случае.
Не
забывайте основное правило:
независимо оттого, завершился ли
поток 2 или про-изошел тайм-аут, вы
не можете управлять моментом
активизации потока 1.
Имена потоков,
CurrentThread и ThreadState
Перед запуском
каждому потоку рекомендуется
присвоить содержательное имя,
поскольку имена значительно
упрощают отладку многопоточных
программ. Для этого следует задать
значение свойства Name командой
следующего вида:
bThread.Name =
"Subtracting thread"
Свойство Thread.CurrentThread
возвращает ссылку на объект потока,
выполняемого в настоящий момент.
Хотя для
отладки многопоточных приложений в
VB .NET существует замечательное окно
потоков, о котором рассказано далее,
нас очень часто выручала команда
MsgBox(Thread.CurrentThread.Name)
Нередко
выяснялось, что код выполняется
совсем не в том потоке, в котором
ему полагалось выполняться.
Напомним, что
термин «недетерминированное
планирование программных потоков»
означает очень простую вещь: в
распоряжении программиста
практически нет средств,
позволяющих влиять на работу
планировщика. По этой причине в
программах часто используется
свойство ThreadState, возвращающее
информацию о текущем состоянии
потока.
Окно потоков (Threads
window) Visual Studio .NET оказывает
неоценимую помощь в отладке
многопоточных программ. Оно
активизируется командой подменю
Debug > Windows в режиме прерывания.
Допустим, вы назначили имя потоку
bThread следующей командой:
bThread.Name =
"Subtracting thread"
Примерный вид
окна потоков после прерывания
программы комбинацией клавиш Ctrl+Break
(или другим способом) показан на рис.
10.5.
Рис. 10.5. Окно
потоков
Стрелкой в
первом столбце помечается активный
поток, возвращаемый свойством Thread.CurrentThread.
Столбец ID содержит числовые
идентификаторы потоков. В
следующем столбце перечислены
имена потоков (если они были
присвоены). Столбец Location указывает
выполняемую процедуру (например,
процедура WriteLine класса Console на рис. 10.5).
Остальные столбцы содержат
информацию о приоритете и
приостановленных потоках (см.
следующий раздел).
Окно потоков (а
не операционная система!) позволяет
управлять потоками вашей программы
при помощи контекстных меню.
Например, вы можете остановить
текущий поток, для чего следует
щелкнуть в соответствующей строке
правой кнопкой мыши и выбрать
команду Freeze (позже работу
остановленного потока можно
возобновить). Остановка потоков
часто используемая при отладке,
чтобы неправильно работающий поток
не мешал работе приложения. Кроме
того, окно потоков позволяет
активизировать другой (не
остановленный) поток; для этого
следует щелкнуть правой кнопкой
мыши в нужной строке и выбрать в
контекстном меню команду Switch To Thread (или
просто сделать двойной щелчок на
строке потока). Как будет показано
далee, это очень удобно при
диагностике потенциальных
взаимных блокировок (deadlocks).
Временно
неиспользуемые потоки можно
перевести в пассивное состояние
методом Slеер. Пассивный поток также
считается заблокированным.
Разумеется, с переводом потока в
пассивное состояние на долю
остальных потоков достанется
больше ресурсов процессора.
Стандартный синтаксис метода Slеер
выглядит следующим образом: Thread.Sleep(интервал_в_миллисекундах)
В результате
вызова Sleep активный поток переходит
в пассивное состояние как минимум
на заданное количество миллисекунд
(впрочем, активизация сразу же
после истечения заданного
интервала не гарантируется).
Обратите внимание: при вызове
метода ссылка на конкретный поток
не передается — метод Sleep
вызывается только для активного
потока.
Другая версия
Sleep заставляет текущий поток
уступить оставшуюся часть
выделенного процессорного времени:
Thread.Sleep(0)
Следующий вариант переводит текущий поток в пассивное состояние на неограниченное время (активизация происходит только при вызове Interrupt):
Thread.Slеер(Timeout.Infinite)
Поскольку
пассивные потоки (даже при
неограниченном времени ожидания)
могут прерываться методом Interrupt,
что приводит к инициированию
исключения ThreadlnterruptExcepti on, вызов Slеер
всегда заключается в блок Try-Catch, как
в следующем фрагменте:
Try
Thread.Sleep(200)
Catch tie As
ThreadlnterruptedException
' Пассивное состояние потока было прервано
Catch e As Exception
'Остальные исключения
End Try
Каждая
программа .NET работает в
программном потоке, поэтому метод
Sleep также используется для
приостановки работы программ (если
пространство имен Threadipg не
импортируется программой,
приходится использовать полное имя
Threading.Thread. Sleep).
Завершение или
прерывание программных потоков
Поток
автоматически завершается при
выходе из метода, указанного при
создании делегата ThreadStart, но иногда
требуется завершить метод (следовательно,
и поток) при возникновении
определенных факторов. В таких
случаях в потоках обычно
проверяется условная переменная, в
зависимости от состояния которой
принимается решение об аварийном
выходе из потока. Как правило, для
этого в процедуру включается цикл Do-While:
Sub ThreadedMethod()
' В
программе необходимо
предусмотреть средства для опроса
' условной
переменной.
' Например,
условную переменную можно оформить
в виде свойства
' и
использовать ссылку на это
свойство в программе.
Do While conditionVariable = False And MoreWorkToDo
' Основной
код
Loop End Sub
На опрос
условной переменной уходит
некоторое время. Постоянный опрос в
условии цикла следует использовать
лишь в том случае, если вы ожидаете
преждевременного завершения
потока.
Если проверка
условной переменной должна
происходить в строго определенном
месте, воспользуйтесь командой If-Then
в сочетании с Exit Sub внутри
бесконечного цикла.
Доступ к
условной переменной необходимо
синхронизировать, чтобы
воздействие со стороны других
потоков не помешало ее нормальному
использованию. Этой важной теме
посвящен раздел «Решение проблемы:
синхронизация».
К сожалению, код
пассивных (или заблокированных
иным образом) потоков не
выполняется, поэтому вариант с
опросом условной переменной для
них не подходит. В этом случае
следует вызвать метод Interrupt для
объектной переменной, содержащей
ссылку на нужный поток.
Метод Interrupt
может вызываться только для
потоков, находящихся в состоянии
Wait, Sleep или Join. Если вызвать Interrupt для
потока, находящегося в одном из
перечисленных состояний, то через
некоторое время поток снова начнет
работать, а исполнительная среда
инициирует в потоке исключение
ThreadlnterruptedExcepti on. Это происходит даже
в том случае, если поток был
переведен в пассивное состояние на
неопределенный срок вызовом Thread.Sleepdimeout.
Infinite). Мы говорим «через некоторое
время», поскольку планирование
потоков имеет недетерминированную
природу. Исключение ThreadlnterruptedExcepti on
перехватывается секцией Catch,
содержащей код выхода из состояния
ожидания. Тем не менее секция Catch
вовсе не обязана завершать поток по
вызову Interrupt — поток обрабатывает
исключение по своему усмотрению.
В.NET метод
Interrupt может вызываться даже для
незаблокированных потоков. В этом
случае поток прерывается при
ближайшей блокировке.
![]() |
![]() |
![]() |