Следующий
шаг: кнопка Show Count
Допустим, вы
решили проявить творческую
фантазию и придать форме вид,
показанный на рис. 10.9. Обратите
внимание: кнопка Show Count пока
недоступна.
Рис. 10.10. Форма
с заблокированной кнопкой
Предполагается,
что отдельный поток выполняет
подсчет и разблокирует недоступную
кнопку. Конечно, это можно сделать;
более того, такая задача возникает
достаточно часто. К сожалению, вы не
сможете действовать наиболее
очевидным образом — организовать
связь вторичного потока с потоком
графического интерфейса, сохраняя
ссылку на кнопку ShowCount в
конструкторе, или даже с
использованием стандартного
делегата. Иначе говоря, никогда не
используйте вариант, приведенный
ниже (основные ошибочные строки
выделены жирным шрифтом).
Public Class
RandomCharacters
Private m_0ata As StringBuilder
Private m_CountDone As Boolean
Private mjength. m_count As Integer
Private m_Button As
Windows.Forms.Button
Public Sub New(ByVa1 n As Integer,_
ByVal b As Windows.Forms.Button)
m_length = n - 1
m_Data = New StringBuilder(mJength)
m_Button = b MakeString()
End Sub
Private Sub MakeString()
Dim I As Integer
Dim myRnd As New Random()
For I = 0 To
m_length
m_Data.Append(Chr(myRnd.Next(65. 90)))
Next
End Sub
Public Sub
StartCount()
GetEes()
End Sub
Private Sub GetEes()
Dim I As Integer
For I = 0 To mjength
If m_Data.Chars(I) =
CChar("E") Then
m_count += 1
End If Next
m_CountDone =True
m_Button.Enabled=True
End Sub
Public Readonly
Property GetCount()As Integer
Get
If Not (m_CountDone)
Then
Throw New Exception("Count
not yet done") Else
Return m_count
End If
End Get
End Property
Public Readonly Property IsDone() As Boolean
Get
Return m_CountDone
End Get
End Property
End Class
Вполне вероятно,
что в некоторых случаях этот код
будет работать. Тем не менее:
Если вы
нарушите эти правила, мы гарантируем,
что в ваших многопоточных
графических программах будут
возникать тонкие, неуловимые
ошибки.
Организовать
взаимодействие объектов с
применением событий тоже не
удастся. 06-работник события
выполняется в том же потоке, в
котором произошел вызов RaiseEvent
поэтому события вам не помогут.
И все же здравый
смысл подсказывает, что в
графических приложениях должны
существовать средства модификации
элементов из другого потока. В .NET
Framework существует поточно-безопасный
способ вызова методов приложений GUI
из другого потока. Для этой цели
используется особый тип делегатов
Method Invoker из пространства имен System.Windows.
Forms. В следующем фрагменте приведен
новый вариант метода GetEes (измененные
строки выделены жирным шрифтом):
Private Sub GetEes()
Dim I As Integer
For I = 0 To
m_length
If m_Data.Chars(I) =
CChar("E")Then
m_count += 1
End If Next
m_CountDone = True
Try
Dim mylnvoker As New Methodlnvoker(AddressOf UpDateButton)
myInvoker.Invoke()
Catch e As ThreadlnterruptedException
'Неудача
End Try
End Sub
Public Sub
UpDateButton()
m_Button.Enabled =True
End Sub
Межпоточные
обращения к кнопке осуществляются
не напрямую, а через Method Invoker. .NET
Framework гарантирует, что этот вариант
безопасен по отношению к потокам.
Почему
при многопоточном
программировании возникает
столько проблем?
Теперь, когда вы
получили некоторое представление о
многопоточном программировании и о
потенциальных проблемах, с ним
связанных, мы решили, что в конце
этой главы будет уместно ответить
на вопрос, вынесенный в заголовок
подраздела.
Одна из причин
заключается в том, что
многопотрчность — процесс
нелинейный, а мы привыкли к
линейной модели программирования.
На первых порах трудно привыкнуть к
самой мысли о том, что выполнение
программы может прерываться
случайным образом, а управление
будет передаваться другому коду.
Однако
существует и другая, более
фундаментальная причина: в наши дни
программисты слишком редко
программируют на ассемблере или
хотя бы просматривают
дизассемблированные результаты
работы компилятора. Иначе им было
бы гораздо проще привыкнуть к мысли,
что одной команде языка высокого
уровня (такого, как VB .NET) могут
соответствовать десятки
ассемблерных инструкций. Поток
может прерываться после любой из
этих инструкций, а следовательно —
и посреди команды высокого уровня.
Но и это не все:
современные компиляторы
оптимизируют быстродействие
программ, а оборудование
компьютера может вмешиваться в
процесс управления памятью. Как
следствие, компилятор или
оборудование может без вашего
ведома изменить порядок команд,
указанный в исходном тексте
программы [ Многие компиляторы
оптимизируют циклические операции
копирования массивов вида for i=0 to n:b(i)=a(i):ncxt.
Компилятор (или даже
специализированное устройство
управления памятью) может просто
создать массив, а потом заполнить
его одной операцией копирования
вместо многократного копирования
отдельных элементов! ].
Надеемся, эти пояснения помогут вам лучше понять, почему многопоточное программирование порождает столько проблем, — или по крайней мере меньше удивляться при виде странного поведения ваших многопоточных программ!