Создание потоков
Начнем с
элементарного примера. Допустим, вы
хотите запустить в отдельном
потоке процедуру, которая в
бесконечном цикле уменьшает
значение счетчика. Процедура
определяется в составе класса:
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 должна работать
в отдельном потоке.
На рис. 10.1
показан пример того, что может
произойти после запуска программы
и ее последующего прерывания
клавишей Ctrl+Break. В нашем случае
новый поток запустился лишь после
того, как счетчик в главном потоке
увеличился до 341!
Рис. 10.1. Простая
многопоточная программно время
работы
Если программа
будет работать в течение
большегошромежутка времени,
результат будет выглядеть примерно
так, как показано на рис. 10.2. Мы
видим, что выполнение запущенного
потока приостанавливается и
управление снова передается
главному потоку. В данном случае
имеет место проявление вытесняющей
мно-гопоточности посредством
квантования времени. Смысл этого
устрашающего термина разъясняется
ниже.
Рис. 10.2.
Переключение между потоками в
простой многопоточной программе
При прерывании
потоков и передаче управления
другим потокам операционная
система использует принцип
вытесняющей многопоточности
посредством квантования времени.
Квантование времени также решает
одну из распространенных проблем,
возникавших прежде в многопоточных
программах, — один поток занимает
все процессорное время и не
уступает управления другим потокам
(как правило, это случается в
интенсивных циклах вроде
приведенного выше). Чтобы
предотвратить монопольный захват
процессора, ваши потоки должны
время от времени передавать
управление другим потокам. Если
программа окажется «несознательной»,
существует другое, чуть менее
желательное решение: операционная
система всегда вытесняет
работающий поток независимо от
уровня его приоритета, чтобы доступ
к процессору был предоставлен
каждому потоку в системе.
Если включить
следующую строку в нашу программу
перед вызовом 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