Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Синхронизация потоков Timers.Timer и Windows Forms

85K
25 июня 2015 года
Иван Лазарев
18 / / 25.06.2015
Доброго времени суток! Сейчас я работаю над проектом, логика которого завязана на 8 таймерах с различным интервалом и различной логикой управления. Раньше мне хватало стандартного контрола за глаза, но сейчас код только благодаря огромными усилиям не превращается в кашу. Соответственно, решил сделать замечательный класс, который может управлять не одним событием, а коллекцией. Делюсь кодом, мне не жалко, тем более, если еще и мне кто-нибудь поможет - будет вообще отлично.
Код:
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
Суть не сложна - в объект таймера можно на ходу добавлять объекты событий, имеющие свое имя, и таймер в событии Tick возвращает объект события. При каждом изменении коллекции, либо свойства Interval у какого-либо члена коллекции вычисляется НОД, определяющий интервал общего таймера - такой подход наиболее эффективен. Проблема крылась там, где я ее не ждал - я специально хотел отвязаться от контрола и использовать класс Timers.Timer, но эта зараза многопоточная и вызов события Tick нужно синхронизировать для его обработки в классе формы. Можно легко включить в конструктор класса ссылку на готовый контрол Timer и использовать его, но это костыль и криво, потому что уже в консольном приложении или в библиотеке его использовать станет затруднительно.
Соответственно, вопрос: как вообще делается синхронизация для вызова событий? Прямую синхронизацию через делегат и InvokeRequired я использую, а тут никак не могу понять, что надо делать. Нету опыта создания контролов с многопоточностью. Подскажите
252
29 июня 2015 года
koderAlex
1.4K / / 07.09.2005
попробуй критическую секцию использовать .
85K
30 июня 2015 года
Иван Лазарев
18 / / 25.06.2015
Цитата: koderAlex
попробуй критическую секцию использовать .

Конкретнее?

252
02 июля 2015 года
koderAlex
1.4K / / 07.09.2005
конкретнее любое обращение к коллекции событий надо обрамлять критической секцией .
иначе при многопоточности будут вылеты или ошибки адресации .
и лучше использовать ммтаймер .
Код:
#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);
тогда внутри TimerProc обрабатываешь входящую очередь на добавление//удаление//изменение коллекции евентов и саму коллекцию .
только теперь уже работу с очередью надо обрамлять критической секцией .
85K
02 июля 2015 года
Иван Лазарев
18 / / 25.06.2015
Я под вечер торможу уже, но все равно. На коллекцию не обращай внимания - я переписал код, он у меня теперь однопоточный, все коллизии изменения коллекции на фоне for each я уладил. Вопрос был совершенно в другом - как вообще делается синхронизация между потоками? Можно даже на мой пример не смотреть, это просто пример. Мне главное понять, как события из разных потоков инкапсулированно приводить к потоку формы, чтобы уже при обработке события в классе формы не было причин использовать Invoke. Вопрос хрестоматийный, но почему-то разжеванного ответа не могу найти
297
08 августа 2015 года
koodeer
1.2K / / 02.05.2009
Мне главное понять, как события из разных потоков инкапсулированно приводить к потоку формы, чтобы уже при обработке события в классе формы не было причин использовать Invoke. Вопрос хрестоматийный, но почему-то разжеванного ответа не могу найти

Самый хрестоматийный способ: InvokeRequired + Invoke.

Но если хочется поездить на велосипеде по граблям, их есть у меня. Можно использовать SynchronizationContext. Вот здесь описание, как использовать. Оригинал на английском.

Однако, лучшее решение - посмотреть в сторону TPL.

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог