Threads & Timers

GornHorse

Centurion
Joined
May 27, 2003
Messages
105
Location
Australia
Hi There,

I have a thread which executes a class. I have another thread which starts a timer (system.timers.time). I also have a textbox on the form which, when the elapsed event of the timer is called, will increment.

Basically, what I want to be able to do is graphically represent to the user that the process is running (because this process can take several hours to run), and to display the elapsed time.

The problem I have, is that the class thread starts, and then the timer thread starts; and the timer clicks over a few increments, and then doesn't get called for a while (while the class is being executed), and then, once the class has finished executing, the timer 'catches up', and quickly increments to the actual elapsed time, and then all threads are stopped as a result of calling the FinishedLoading event handler.

I don't know how I should call get this working so that the timer can continue to increment whilst the class is being executed.

My code snippet is as follows:

Code:
public Thread1 as Threading.Thread
public ThreadTimer as Threading.Thread
private event FinishedLoading()
dim Timer as Timers.Timer

Public Sub New(byval parent as frmMain)
  Thread1 = new System.Threading.Thread(AddressOf CreateDDBatch)
  ThreadTimer = new System.Threading.Thread(AddressOf Timer1Start)
  AddHandler FinishedLoading, AddressOf FinishedLoadingEH
  Thread1.Start()
  ThreadTimer.Start()
End Sub

Private Sub CreateDDBatch()
  Dim DDProcessor as new clsID_DD_AutoProcessor(username, password)
  RaiseEvent FinishedLoading()
End Sub

Sub FinishedLoadingEH()
  ThreadTimer.Abort()
  Timer1.Stop()
  Me.Cursor = Windows.Forms.Cursors.Default
  Me.btnClose.Enabled = True
  Thread1.Abort()
End Sub

Private Sub Timer1Start()
  Timer = New System.Timers.Timer(100)
  AddHandler Timer.Elapsed, AddressOf TimerTick
  Timer.Start()
End Sub

Private Sub TimerTick(ByVal sender as Object, ByVal e as Timers.ElapsedEventArgs)
  Try
    tbTimer.text = cint(tbTimer.text) + 1
  Catch ex as Exception
  End Try
End Sub


Please try and respond asap with some assistance.

Thanks very much,
Michelle
 
Further to problem...

Hi there.

Well, I have figured out what the problem is, but don't know how to fix it!!

I have slightly modified the code from above, such that the timer does not run on a separate thread - i just call Timer1Start() instead of ThreadTimer.Start().

Now, in remembering that, the timer does appear to handle the timer.elapsed event continually whilst the thread is running (calling the class).

The reason that I know this is because I created a small console application using exactly the same code as my windows app, and dumped to the console a) when the thread begins, b) an incrementing int when the timer.elapsed event is called, and, c) when the thread finishes.

This shows the output as...
Starting the Thread...
Starting the Timer...
****************
Calling the Class
1
2
3
4
.
.
.
7428
7429
Finished Calling the Class.

Therefore, the timer does increment whilst the thread is running.

My problem now appears to be that when the timer.elapsed event is handles, I want a textbox on my windows form to increment each time the event is handled so the user knows something is happening.
What is happening, though, is that once the class is called, the textbox stops refreshing with the new value. It only catches up once the class is finished being called.

I tried using the following lines of code, which, according to other threads, should have worked:
Code:
Timer.SynchronizingObject = Me
and
Code:
Timer.SynchronizingObject = tbTimer
But, neither of them made the program run any differently.


How on earth can I get the tbTimer textbox to display the increments each time the timer.elapsed event is handled - because, the event is being handled, the textbox just isn't displaying the new text.


Thanks in advance,
Michelle


PS: Please respond ASAP, this is rather urgent!!
 
Hi Michelle,

for me it is a little bit unclear what you want do do with
your code. You say "executing the class".... !?!?

My idea is: Try to make another project in which you
start again with a very easy version of that problem.
Threading can be a difficult thing and you must bear in mind,
that you cannot change a control (e.g. a textbox) from
another thread so easy. You HAVE TO USE BeginInvoke()
or Invoke() to make changes to the so called "UI-thread",
that is the thread, in which all your controls run.

Try to read some things in the net:

http://www.yoda.arachsys.com/csharp/multithreading.html

http://msdn.microsoft.com/library/d...n-us/dnnetcomp/html/netcfmultithreadedapp.asp


Info from MSDN:

>>
Controls in Windows Forms are bound to a specific thread and are not thread safe. Therefore, if you are calling a control's method from a different thread, you must use one of the control's invoke methods to marshal the call to the proper thread. This property can be used to determine if you must call an invoke method, which can be useful if you do not know what thread owns a control. There are four methods on a control that are safe to call from any thread: Invoke, BeginInvoke, EndInvoke and CreateGraphics. For all other method calls, you should use one of these invoke methods when calling from a different thread.

Windows Forms uses the single-threaded apartment (STA) model because Windows Forms is based on native Win32 windows that are inherently apartment-threaded. The STA model implies that a window can be created on any thread, but it cannot switch threads once created, and all function calls to it must occur on its creation thread. Outside Windows Forms, classes in the .NET Framework use the free threading model.
<<
 
Last edited:
Hi Toni,

To explain what it is that I am trying to do...

I have a thread, call this ThreadClassRunner. When the ThreadClassRunner starts, it runs a particular function from a referenced class.

I also have a Timers.Timer that is to tick over to calculate the elapsed time taken for the function to complete - this can be up to several hours.

I have a textbox on my form that I want to update each time the timer.elapsed event fires so that the users are aware of a) the fact that the function is still running, and b) to inform them of how long the function is taking to run.

I know that the elapsed event continues to fire while the ThreadClassRunner is running because i a) created a console application that dumped output to the screen so I know what was happening, and this indicated that yes, the elapsed event continues to be handled correctly while the ThreadClassRunner is running. And, b) within my actual application, i ran a trace to the event log, and this also indicated that the elapsed event was correctly being handled while the ThreadClassRunner was running.

So, the timer is correctly handling the elapsed event. My problem is that the textbox (which i want the elapsed time to display in) is not refreshing each time the elapsed event is being handled.

I can't understand WHY this is happening, and I haven't been able to find to much info about how to fix it.

I don't understand how the textbox.Invoke() method works, and therefore, would really appreciate some more help with this.

Thanks.


BTW, I have been looking though the info that you provided links to, and it just seems to be help on the threading. I have the threading working - i just need to get the display working. Please remember that I do not create the timer on a thread, therefore, I would assume that the elapsed event is not being handled on a thread either. The only object in the application that is running on a thread is the class's function which runs on the ThreadClassRunner.
 
When you create a new thread, the main thread is still running. The timer does not and should not have it's own thread. Simple create a new thread for the worker function as you are and then create a loop that checks to see if that thread has finished, if not print the elapsed time to a label:


Code:
public Thread1 as Threading.Thread
private event FinishedLoading()
dim Timer as Timers.Timer

Public Sub New(byval parent as frmMain)
  Thread1 = new System.Threading.Thread(AddressOf CreateDDBatch)
  AddHandler FinishedLoading, AddressOf FinishedLoadingEH

  Timer = New System.Timers.Timer()
  Timer.Start()
  Thread1.Start()

  While (Thread1.Join(1000))

'this next line may be wrong, edit
   tbTimer.Text = "Hours: " + Cstr(Date.Now.Hours - Timer.Hours) + " Minutes: " + Cstr(Date.Now.Minutes - Timer.Minutes) + "   Seconds: " + Cstr(Date.Now.Seconds - Timer.Second)
  End While

End Sub

Private Sub CreateDDBatch()
  Dim DDProcessor as new clsID_DD_AutoProcessor(username, password)
  RaiseEvent FinishedLoading()
End Sub

Sub FinishedLoadingEH()
  Me.Cursor = Windows.Forms.Cursors.Default
  Me.btnClose.Enabled = True
End Sub
 
Hi there...

Ok, so there have been a few bits of info here... but, I am a bit confused now.

I know what I need to do, and that is to get the textbox to display the elapsed time each time the timer.elapsed event is handled. Forget about the threading - I've got that working. I just need a piece of code to help me get the textbox to display the text properly.

Now, I am assuming from what I have read that I need to use the textbox.Invoke() method, but, I have no idea what to do with it!
I have to pass it a system.delegate, but what is it? You've seen my code above, should I pass it one of those methods?

Please help me with this - I really need to get this working.


Thanks,
Michelle
 
Ok... So, to probably make things worse... this is my new code. Bare in mind that this is still not making the textbox display the text each time the timer.elapsed event is handled, but I think i'm headed in the right direction - but I definately still need more help!!

Code:
    Dim myMainFrm As MADUSA_MainMenu
    Public Thread1 As Threading.Thread
    Dim MADConnector As clsMADConnObj.Connection
    Private Event FinishedLoading()

    Dim curtime As DateTime
    Dim Timer As Timers.Timer

    Dim t_StartTime As DateTime
    Dim t As TimeSpan

    Dim m_counter As Integer


    Public Sub New(ByVal parent As MADUSA_MainMenu)
        MyBase.New()
        myMainFrm = parent
        MADConnector = myMainFrm.GetMADConnector

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
        Thread1 = New System.Threading.Thread(AddressOf CreateDDBatch)
        Thread1.Name = "Create DD Batch Thread"
        AddHandler FinishedLoading, AddressOf FinishedLoadingEH
        t_StartTime = System.DateTime.Now
        Timer1Start()
        Thread1.Start()

    End Sub


    Private Sub CreateDDBatch()
        Dim DDProcessor As New clsID_DirectDebit_AutoProcessor.AutoProcessor(MADConnector, myMainFrm.GetUsername, myMainFrm.GetPassword)
        RaiseEvent FinishedLoading()
    End Sub

    Sub FinishedLoadingEH()
        Timer.Enabled = False
        Timer.Stop()

        Me.Cursor = Windows.Forms.Cursors.Default
        Me.btnClose.Enabled = True
        Thread1.Abort()

    End Sub

    Private Sub Timer1Start()
        Timer = New System.Timers.Timer(500)
        Timer.Enabled = True
        Timer.SynchronizingObject = myMainFrm
        AddHandler Timer.Elapsed, AddressOf TimerTick
        Timer.Enabled = True

        Timer.Start()
    End Sub

    Private Sub TimerTick(ByVal sender As System.Object, ByVal e As System.Timers.ElapsedEventArgs)
        Try
            Dim t_CurrentTime As DateTime = e.SignalTime
            Dim t_ElapsedTime As TimeSpan = t_CurrentTime.Subtract(t_StartTime)
            t = t_ElapsedTime

            UseDelegate()

        Catch ex As Exception
        End Try
    End Sub


    Delegate Sub UpdateTextbox(ByVal str As String)

    Private Function UseDelegate()
        Dim objUpdater As New Updater(Me)

        Dim objDelegator As UpdateTextbox
        objDelegator = New UpdateTextbox(AddressOf objUpdater.WriteSomething)

        objDelegator.Invoke(t.ToString)

    End Function

    Class Updater

        Dim sub_Parent As MADUSA_DirectDebitProcess_Timer

        Public Sub New(ByVal subp As MADUSA_DirectDebitProcess_Timer)
            sub_Parent = subp
        End Sub

        Public Sub WriteSomething(ByVal strToWrite As String)
            sub_Parent.tbTimer.Text = strToWrite
        End Sub
    End Class

Perhaps with this code, someone may be able to give me a hint??!!

Thanks again,
Michelle
 
Back
Top