1

I'm working on a project that will require me to react to 5 separate events generated by an external device and then do something after a certain delay. The events will normally happen one at a time but may occasionally be simultaneous. Is this a bad idea and if so why?

Imports System.Windows.Threading
Class MainWindow

Private TimerList As List(Of DispatcherTimer)

Private Sub Window_Loaded(ByVal sender As System.Object, 
ByVal e As ystem.Windows.RoutedEventArgs) Handles MyBase.Loaded

TimerList = New List(Of DispatcherTimer)

    'Create 5 timers for the 5 events from external device
    For i As Integer = 1 To 5
        TimerList.Add(
            New DispatcherTimer(TimeSpan.FromSeconds(10), DispatcherPriority.Normal,
                            New System.EventHandler(AddressOf tmr_Tick),
                            Me.Dispatcher) With {.Tag = i}
                        )
    Next

End Sub

Public Sub SomeEventFromExternalDevice(ByVal ID As Integer) 'handles...

    Dim CurTimer = TimerList.Find(Function(x) x.Tag = ID)

    If Not CurTimer Is Nothing Then
        CurTimer.Start()
    End If

End Sub

Private Sub tmr_Tick(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim ID = DirectCast(sender.tag, Integer)

    Dim curTimer = TimerList.Find(Function(x) x.Tag = ID)

    curTimer.Stop()

    Select Case ID
        'change something on the UI to indicate that event x timer has elapsed
    End Select

End Sub

End Class 
jweaver
  • 661
  • 5
  • 15
  • If the events from the external device are coming from separate threads (ie: can be truly simultaneous) then you should probably have your event handler invoke a delegate on the thread which contains your dispatch timers. You should also invoke even if all five events are being raised from a single separate thread (no true simultaneity). – J... Jan 06 '12 at 14:37
  • Also, if a single event fires again before the timer delay then you will be starting an already started timer, doing nothing. This makes you blind to events which occur at a rate faster than your timer delay. Better, perhaps, to create a new timer each time an event fires and dispose of it once it finishes. – J... Jan 06 '12 at 14:38
  • The events from the external device will be from a single thread so you are right they will not fire simultaneous. But another event may fire before the timer has elapsed. I like the idea of creating the timer when the event fires. But how can I dispose when it finishes? From the tick event handler? And is it ok to point all of these timers to a single handler sub? – jweaver Jan 06 '12 at 15:15

1 Answers1

1

Probably a better solution to this is to get rid of the DispatcherTimer altogether and do this with plain threads.

First create a class to hold a single event work package - I've added a way to pass the delay value here but you can omit it if it doesn't fit your needs. This is a simple procedure which sleeps the thread for the delay and then raises a new event for you to catch. If you need precision then you can implement the delay with a Stopwatch or something else:

Public Class DelayThread
    ' Task information
    Private _ID As Integer
    Private _delay As Integer
    Event onDelayUp(ByVal ID As Integer)

    ' Constructor
    Public Sub New(ByVal myID As Integer, ByVal myDelay As Integer)
        _ID = myID
        _delay = myDelay
    End Sub

    Public Sub DelayProc()
        System.Threading.Thread.Sleep(_delay)
        RaiseEvent onDelayUp(_ID)
    End Sub
End Class

Now your device will fire events handled here:

Public Sub SomeEventFromExtDevice(ByVal ID As Integer) 'Handles ...
        'get a "delay" value from somewhere, if you like
        Dim newDelayTask As New DelayThread(ID, delay)  'added delay here

        AddHandler newDelayTask.onDelayUp, AddressOf DoSomething

        Dim t As New System.Threading.Thread( _
        New System.Threading.ThreadStart(AddressOf newDelayTask.DelayProc))

        t.Priority = Threading.ThreadPriority.Normal 'whatever priority
        t.Start()

End Sub

This way, each time an event fires you start a new thread which waits for your delay time, does DoSomething and then terminates, cleaning itself up in the process.

Here you will need some "DoSomething" procedure :

 'declare this somewhere
 Delegate Sub dlgDoSomething(ByVal ID As Integer)

 Public Sub DoSomething(ByVal ID As Integer) 'hooked to onDelayUp @ runtime above
      'using "Me" here - if DoSomething is somewhere else 
      'you may need to refer to the main UI form instead  
      Me.Invoke(New dlgDoSomething(AddressOf uiDoSomething), New Object() {ID})
 End Sub

The DoSomething procedure will be called from each thread so it has to invoke on the main UI thread - then need :

Public Sub uiDoSomething(ByVal ID As Integer)
        ' Do Something with ID - UI thread is now executing this so you are safe
End Sub

If knowing the exact order of the events is important - knowing when they arrived and in what order - then you could add a timestamp in the SomeEventFromExtDevice and pass that along also.

You might also want to add some handling for shutting down the application - there are no checks here to make sure that threads are not trying to marshall onto the main form after it has been disposed, for example.

J...
  • 30,968
  • 6
  • 66
  • 143
  • Thanks Justin. I appreciate the help. I can see how this would avoid the problems pointed out earlier. – jweaver Jan 06 '12 at 20:54