Синхронизация потоков Timers.Timer и Windows Forms
Код:
Public Class ExTimer
Public Event EnabledChange(Enabled As Boolean)
Public Event Tick(EventObject As TimerEvent)
Private Events As New Collections.Generic.List(Of TimerEvent)
Private _enabled As Boolean, _int As Integer, _started As Boolean
Private WithEvents _timer As New Timers.Timer
Public Sub AddEvent(NewEvent As TimerEvent)
Events.Add(NewEvent)
_int = GetInterval()
_timer.Interval = _int
AddHandler NewEvent.ChangeInterval, AddressOf Event_IntervalChanged
End Sub
Public Sub DeleteEvent(EventName As String)
If Events.Count <= 0 Then Throw New Exception("Коллекция событий таймера пуста")
For Each evt As TimerEvent In Events
If evt.Name = EventName Then Events.Remove(evt)
_int = GetInterval()
_timer.Interval = _int
Next
End Sub
Public Sub DeleteEvent(EventObject As TimerEvent)
If Events.Count <= 0 Then Throw New Exception("Коллекция событий таймера пуста")
If Events.Contains(EventObject) Then
Events.Remove(EventObject)
_int = GetInterval()
_timer.Interval = _int
Else
Throw New Exception("События с таким именем не найдено")
End If
End Sub
Public Function GetEvent(EventName As String) As TimerEvent
If Events.Count <= 0 Then Throw New Exception("Коллекция событий таймера пуста")
Dim rez As TimerEvent
For i As Integer = 0 To Events.Count
If Events.Item(i).name = EventName Then
rez = Events.Item(i)
Exit For
End If
Next
If IsNothing(rez) Then Throw New Exception("События с таким именем не найдено")
Return rez
End Function
Public Sub Start()
_started = True
Enabled = True
_started = False
_int = GetInterval()
If _int = 0 Then Exit Sub
_timer = New Timers.Timer(_int)
_timer.AutoReset = True
_timer.Start()
End Sub
Public Sub [Stop]()
_started = True
Enabled = False
_started = False
_timer.Stop()
End Sub
Public Property Enabled As Boolean
Get
Return _enabled
End Get
Set(value As Boolean)
_enabled = value
RaiseEvent EnabledChange(_enabled)
If _started Then Exit Property
If value Then Start() Else Stop
End Set
End Property
Private Sub Event_IntervalChanged()
_int = GetInterval()
_timer.Interval = _int
End Sub
Private Function GetInterval() As Integer
Dim p, rez As Integer
If Events.Count = 0 Then Return 0
If Events.Count = 1 Then Return Events.Item(0).interval
rez = NOD(Events.Item(0).interval, Events.Item(1).interval)
If Events.Count = 2 Then Return rez
For i As Integer = 2 To Events.Count - 1
rez = NOD(rez, Events.Item(i).interval)
If i = Events.Count - 1 Then Return rez
Next
End Function
Private Function NOD(a As Integer, b As Integer) As Integer
Dim x, y, z, rez As Integer
x = a : y = b : rez = 1
Do
If x = 0 Then Return y * rez
If y = 0 Then Return x * rez
If x = y Then Return x * rez
If x = 1 Then Return 1 * rez
If y = 1 Then Return 1 * rez
If chet(x) = True And chet(y) = True Then
rez = rez * 2
x = x / 2
y = y / 2
End If
If chet(x) = True And chet(y) = False Then x = x / 2
If chet(x) = False And chet(y) = True Then y = y / 2
If chet(x) = False And chet(y) = False And y < x Then x = (x - y) / 2
If chet(x) = False And chet(y) = False And y > x Then
z = x
x = (y - x) / 2
y = z
End If
Loop
End Function
Private Function chet(num As Integer) As Boolean
If (num / 2) - (num 2) = 0 Then Return True Else Return False
End Function
Public Class TimerEvent
Private _name As String, _inter As Integer, _enabled As Boolean
Public ElapseInterval As Integer
#Region "Event"
Public Event ChangeName(OldName As String, NewName As String)
Public Event ChangeInterval(Name As String, Interval As String)
Public Event ChangeEnabled(Name As String, Enabled As Boolean)
#End Region
#Region "New"
Public Sub New(Name As String)
_name = Name
_enabled = True
End Sub
Public Sub New(Name As String, Interval As Integer)
_name = Name
_inter = Interval
ElapseInterval = Interval
_enabled = True
End Sub
Public Sub New(Name As String, Interval As Integer, Enabled As Boolean)
_name = Name
_inter = Interval
_enabled = Enabled
ElapseInterval = Interval
End Sub
#End Region
#Region "Property"
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
Dim old As String = _name
_name = value
RaiseEvent ChangeName(old, _name)
End Set
End Property
Public Property Interval As Integer
Get
Return _inter
End Get
Set(value As Integer)
_inter = value
ElapseInterval = value
RaiseEvent ChangeInterval(_name, _inter)
End Set
End Property
Public Property Enabled As Boolean
Get
Return _enabled
End Get
Set(value As Boolean)
_enabled = value
RaiseEvent ChangeEnabled(_name, _enabled)
End Set
End Property
#End Region
End Class
Private Sub _timer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles _timer.Elapsed
_timer.Stop()
For Each evt As TimerEvent In Events
If evt.Enabled = False Then Continue For
evt.ElapseInterval = evt.ElapseInterval - _int
If evt.ElapseInterval <= 0 Then
evt.ElapseInterval = evt.Interval
Try
_evt = evt
'ЗДЕСЬ ДОЛЖНА БЫТЬ СИНХРОНИЗАЦИЯ (raiseevent Tick(evt))
Catch ex As Exception
Trace.WriteLine(ex.Message)
End Try
End If
Next
_timer.Start()
End Sub
End Class
Public Event EnabledChange(Enabled As Boolean)
Public Event Tick(EventObject As TimerEvent)
Private Events As New Collections.Generic.List(Of TimerEvent)
Private _enabled As Boolean, _int As Integer, _started As Boolean
Private WithEvents _timer As New Timers.Timer
Public Sub AddEvent(NewEvent As TimerEvent)
Events.Add(NewEvent)
_int = GetInterval()
_timer.Interval = _int
AddHandler NewEvent.ChangeInterval, AddressOf Event_IntervalChanged
End Sub
Public Sub DeleteEvent(EventName As String)
If Events.Count <= 0 Then Throw New Exception("Коллекция событий таймера пуста")
For Each evt As TimerEvent In Events
If evt.Name = EventName Then Events.Remove(evt)
_int = GetInterval()
_timer.Interval = _int
Next
End Sub
Public Sub DeleteEvent(EventObject As TimerEvent)
If Events.Count <= 0 Then Throw New Exception("Коллекция событий таймера пуста")
If Events.Contains(EventObject) Then
Events.Remove(EventObject)
_int = GetInterval()
_timer.Interval = _int
Else
Throw New Exception("События с таким именем не найдено")
End If
End Sub
Public Function GetEvent(EventName As String) As TimerEvent
If Events.Count <= 0 Then Throw New Exception("Коллекция событий таймера пуста")
Dim rez As TimerEvent
For i As Integer = 0 To Events.Count
If Events.Item(i).name = EventName Then
rez = Events.Item(i)
Exit For
End If
Next
If IsNothing(rez) Then Throw New Exception("События с таким именем не найдено")
Return rez
End Function
Public Sub Start()
_started = True
Enabled = True
_started = False
_int = GetInterval()
If _int = 0 Then Exit Sub
_timer = New Timers.Timer(_int)
_timer.AutoReset = True
_timer.Start()
End Sub
Public Sub [Stop]()
_started = True
Enabled = False
_started = False
_timer.Stop()
End Sub
Public Property Enabled As Boolean
Get
Return _enabled
End Get
Set(value As Boolean)
_enabled = value
RaiseEvent EnabledChange(_enabled)
If _started Then Exit Property
If value Then Start() Else Stop
End Set
End Property
Private Sub Event_IntervalChanged()
_int = GetInterval()
_timer.Interval = _int
End Sub
Private Function GetInterval() As Integer
Dim p, rez As Integer
If Events.Count = 0 Then Return 0
If Events.Count = 1 Then Return Events.Item(0).interval
rez = NOD(Events.Item(0).interval, Events.Item(1).interval)
If Events.Count = 2 Then Return rez
For i As Integer = 2 To Events.Count - 1
rez = NOD(rez, Events.Item(i).interval)
If i = Events.Count - 1 Then Return rez
Next
End Function
Private Function NOD(a As Integer, b As Integer) As Integer
Dim x, y, z, rez As Integer
x = a : y = b : rez = 1
Do
If x = 0 Then Return y * rez
If y = 0 Then Return x * rez
If x = y Then Return x * rez
If x = 1 Then Return 1 * rez
If y = 1 Then Return 1 * rez
If chet(x) = True And chet(y) = True Then
rez = rez * 2
x = x / 2
y = y / 2
End If
If chet(x) = True And chet(y) = False Then x = x / 2
If chet(x) = False And chet(y) = True Then y = y / 2
If chet(x) = False And chet(y) = False And y < x Then x = (x - y) / 2
If chet(x) = False And chet(y) = False And y > x Then
z = x
x = (y - x) / 2
y = z
End If
Loop
End Function
Private Function chet(num As Integer) As Boolean
If (num / 2) - (num 2) = 0 Then Return True Else Return False
End Function
Public Class TimerEvent
Private _name As String, _inter As Integer, _enabled As Boolean
Public ElapseInterval As Integer
#Region "Event"
Public Event ChangeName(OldName As String, NewName As String)
Public Event ChangeInterval(Name As String, Interval As String)
Public Event ChangeEnabled(Name As String, Enabled As Boolean)
#End Region
#Region "New"
Public Sub New(Name As String)
_name = Name
_enabled = True
End Sub
Public Sub New(Name As String, Interval As Integer)
_name = Name
_inter = Interval
ElapseInterval = Interval
_enabled = True
End Sub
Public Sub New(Name As String, Interval As Integer, Enabled As Boolean)
_name = Name
_inter = Interval
_enabled = Enabled
ElapseInterval = Interval
End Sub
#End Region
#Region "Property"
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
Dim old As String = _name
_name = value
RaiseEvent ChangeName(old, _name)
End Set
End Property
Public Property Interval As Integer
Get
Return _inter
End Get
Set(value As Integer)
_inter = value
ElapseInterval = value
RaiseEvent ChangeInterval(_name, _inter)
End Set
End Property
Public Property Enabled As Boolean
Get
Return _enabled
End Get
Set(value As Boolean)
_enabled = value
RaiseEvent ChangeEnabled(_name, _enabled)
End Set
End Property
#End Region
End Class
Private Sub _timer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles _timer.Elapsed
_timer.Stop()
For Each evt As TimerEvent In Events
If evt.Enabled = False Then Continue For
evt.ElapseInterval = evt.ElapseInterval - _int
If evt.ElapseInterval <= 0 Then
evt.ElapseInterval = evt.Interval
Try
_evt = evt
'ЗДЕСЬ ДОЛЖНА БЫТЬ СИНХРОНИЗАЦИЯ (raiseevent Tick(evt))
Catch ex As Exception
Trace.WriteLine(ex.Message)
End Try
End If
Next
_timer.Start()
End Sub
End Class
Соответственно, вопрос: как вообще делается синхронизация для вызова событий? Прямую синхронизацию через делегат и InvokeRequired я использую, а тут никак не могу понять, что надо делать. Нету опыта создания контролов с многопоточностью. Подскажите
попробуй критическую секцию использовать .
Цитата: koderAlex
попробуй критическую секцию использовать .
Конкретнее?
иначе при многопоточности будут вылеты или ошибки адресации .
и лучше использовать ммтаймер .
Код:
#include <mmsystem.h>
int MMTimer;
...
void CALLBACK TimerProc(unsigned int uID,unsigned int uMsg,DWORD dwUser,DWORD dw1,DWORD dw2)
{
...
}
...
//создаём таймер
MMTimer=timeSetEvent(1,0,TimerProc,NULL,TIME_PERIODIC);
//теперь TimerProc периодически вызывается каждую миллисекунду с приоритетом 15
...
//убиваем таймер
timeKillEvent(MMTimer);
int MMTimer;
...
void CALLBACK TimerProc(unsigned int uID,unsigned int uMsg,DWORD dwUser,DWORD dw1,DWORD dw2)
{
...
}
...
//создаём таймер
MMTimer=timeSetEvent(1,0,TimerProc,NULL,TIME_PERIODIC);
//теперь TimerProc периодически вызывается каждую миллисекунду с приоритетом 15
...
//убиваем таймер
timeKillEvent(MMTimer);
только теперь уже работу с очередью надо обрамлять критической секцией .
Я под вечер торможу уже, но все равно. На коллекцию не обращай внимания - я переписал код, он у меня теперь однопоточный, все коллизии изменения коллекции на фоне for each я уладил. Вопрос был совершенно в другом - как вообще делается синхронизация между потоками? Можно даже на мой пример не смотреть, это просто пример. Мне главное понять, как события из разных потоков инкапсулированно приводить к потоку формы, чтобы уже при обработке события в классе формы не было причин использовать Invoke. Вопрос хрестоматийный, но почему-то разжеванного ответа не могу найти
Цитата: Иван Лазарев
Мне главное понять, как события из разных потоков инкапсулированно приводить к потоку формы, чтобы уже при обработке события в классе формы не было причин использовать Invoke. Вопрос хрестоматийный, но почему-то разжеванного ответа не могу найти
Самый хрестоматийный способ: InvokeRequired + Invoke.
Но если хочется поездить на велосипеде по граблям, их есть у меня. Можно использовать SynchronizationContext. Вот здесь описание, как использовать. Оригинал на английском.
Однако, лучшее решение - посмотреть в сторону TPL.