Создание
потоков
Начнем с
элементарного примера. Допустим, вы
хотите запустить в отдельном
потоке процедуру, которая в
бесконечном цикле уменьшает
значение счетчика. Процедура
определяется в составе класса:
Public Class
WillUseThreads
Public Sub SubtractFromCounter()
Dim count As Integer
Do While True count -= 1
Console.WriteLlne("Am
in another thread and counter ="
& count)
Loop
End Sub
End Class
Поскольку
условие цикла Do остается истинным
всегда, можно подумать, что ничто не
помешает выполнению процедуры
SubtractFromCounter. Тем не менее в
многопоточном приложении это не
всегда так.
В следующем
фрагменте приведена процедура Sub
Main, запускающая поток, и команда
Imports:
Option Strict On
Imports System.Threading Module Modulel
Sub Main()
1 Dim myTest As New
WillUseThreads()
2 Dim bThreadStart As New ThreadStart(AddressOf _
myTest.SubtractFromCounter)
3 Dim bThread As New
Thread(bThreadStart)
4 ' bThread.Start()
Dim i As Integer
5 Do While True
Console.WriteLine("In main thread and count is " & i) i += 1
Loop
End Sub
End Module
Давайте
последовательно разберем наиболее
принципиальные моменты. Прежде
всего процедура Sub Man n всегда
работает в главном потоке (main
thread). В програм-мах .NET всегда
работают минимум два потока:
главный и поток сборки мусора. В
строке 1 создается новый экземпляр
тестового класса. В строке 2 мы
создаем делегат ThreadStart и передаем
адрес процедуры SubtractFromCounter
экземпляра тестового класса,
созданного в строке 1 (эта процедура
вызывается без параметров).
Благодаря импортированию
пространства имен Threading длинное имя
можно не указывать. Объект нового
потока создается в строке 3.
Обратите внимание на передачу
делегата ThreadStart при вызове
конструктора класса Thread. Некоторые
программисты предпочитают
объединять эти две строки в одну
логическую строку:
Dim bThread As New Thread(New ThreadStarttAddressOf _
myTest.SubtractFromCounter))
Наконец, строка
4 «запускает» поток, для чего
вызывается метод Start экземпляра
класса Thread, созданного для делегата
ThreadStart. Вызывая этот метод, мы
указываем операционной системе,
что процедура Subtract должна работать
в отдельном потоке.
Слово «запускает»
в предыдущем абзаце заключено в
кавычки, поскольку в этом случае
наблюдается одна из многих
странностей многопоточного
программирования: вызов Start не
приводит к фактическому запуску
потока! Он всего лишь сообщает, что
операционная система должна
запланировать выполнение
указанного потока, но
непосредственный запуск находится
вне контроля программы. Вам не
удастся начать выполнение потоков
по своему усмотрению, потому что
выполнением потоков всегда
распоряжается операционная
система. В одном из дальнейших
разделов вы узнаете, как при помощи
приоритета заставить операционную
систему побыстрее запустить ваш
поток.
На рис. 10.1
показан пример того, что может
произойти после запуска программы
и ее последующего прерывания
клавишей Ctrl+Break. В нашем случае
новый поток запустился лишь после
того, как счетчик в главном потоке
увеличился до 341!
Рис. 10.1. Простая
многопоточная программно время
работы
Если программа
будет работать в течение
большегошромежутка времени,
результат будет выглядеть примерно
так, как показано на рис. 10.2. Мы
видим, что выполнение запущенного
потока приостанавливается и
управление снова передается
главному потоку. В данном случае
имеет место проявление вытесняющей
мно-гопоточности посредством
квантования времени. Смысл этого
устрашающего термина разъясняется
ниже.
Рис. 10.2.
Переключение между потоками в
простой многопоточной программе
При прерывании
потоков и передаче управления
другим потокам операционная
система использует принцип
вытесняющей многопоточности
посредством квантования времени.
Квантование времени также решает
одну из распространенных проблем,
возникавших прежде в многопоточных
программах, — один поток занимает
все процессорное время и не
уступает управления другим потокам
(как правило, это случается в
интенсивных циклах вроде
приведенного выше). Чтобы
предотвратить монопольный захват
процессора, ваши потоки должны
время от времени передавать
управление другим потокам. Если
программа окажется «несознательной»,
существует другое, чуть менее
желательное решение: операционная
система всегда вытесняет
работающий поток независимо от
уровня его приоритета, чтобы доступ
к процессору был предоставлен
каждому потоку в системе.
Поскольку
в схемах квантования всех версий
Windows, в которых работает .NET, каждо-му
потоку выделяется минимальный
квант времени, в программировании .NET
проблемы с монопольным захватом
процессора не столь серьезны. С
другой стороны, если среда .NET когда-нибудь
будет адаптирована для других
систем, ситуация может измениться.
Если включить
следующую строку в нашу программу
перед вызовом Start, то даже потоки,
обладающие минимальным
приоритетом, получат некоторую
долю процессорного времени:
bThread.Priority =
ThreadPriority.Highest
Рис. 10.3. Поток
с максимальным приоритетом обычно
начинает работать быстрее
Рис. 10.4. Процессор
предоставляется и потокам с более
низким приоритетом
Команда
назначает новому потоку
максимальный приоритет и уменьшает
приоритет главного потока. Из рис. 10.3
видно, что новый поток начинает
работать быстрее, чем прежде, но,
как показывает рис. 10.4, главный
поток тоже получает управление (правда,
очень ненадолго и лишь после
продолжительной работы потока с
вычитанием). При запуске программы
на ваших компьютерах будут
получены результаты, похожие на
показанные на рис. 10.3 и 10.4, но из-за
различий между нашими системами
точного совпадения не будет.
В перечисляемый
тип ThreadPrlority входят значения для
пяти уровней приоритета:
ThreadPriority.Highest
ThreadPriority.AboveNormal
ThreadPrlority.Normal
ThreadPriority.BelowNormal
ThreadPriority.Lowest