Visual Basic Development Bookmark and Share   
 Home > Visual Basic Language > Timer control don't work when running paralell thread
 

Timer control don't work when running paralell thread

I have this class, His New() constructor is called from MAIN_Form:
Code:
Imports System.Threading
Public Class ThisClass
Public Shared ProgressBarValue As Integer

Public Sub New()
Dim TimerDoevents As New System.Windows.Forms.Timer
AddHandler TimerDoevents.Tick, AddressOf TimerDoEvents_Tick

With TimerDoevents
.Interval = 500

.Enabled = True
Dim TimerThread As New Thread(AddressOf .Start)
TimerThread.IsBackground = True
TimerThread.Start()


Dim HeavySub As New Thread(AddressOf LoopPrincipal)
With HeavySub
.IsBackground = True
.Start()
'MsgBox("")
.Join()

End With 'HeavySub

.Stop()
.Enabled = False
End With 'TimerDoevents


End Sub

Private Sub LoopPrincipal()
For Each Element As Something In VeryLargeArray
'Do something
ProgressBarValue += 1
Next Element
End Sub 'LoopPrincipal


Private Sub TimerDoEvents_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) 'Handles TimerDoEvents.Tick
With MAIN_Form
.ProgressBarArchivos.Value = Interlocked.Read(ThisClass.ProgressBarValue)
.Refresh()
End With
Application.DoEvents()
End Sub

End Class
(Timer code is red)
(LoopPrincipal is blue)
The entire purpose of this class, is to run a Large For..Next loop in the sub LoopPrincipal

The Objective is, I need to update a progress bar in MAIN_Form to reflect the For..Next progress.

I could add an Application.DoEvents in the loop, but that eats a lot of time, since the line would be run millions of times.
So, I could add some If... then...EndIf to reduce the number of times Application.DoEvents is executed.
But the If line, is also heavy, because is run millions of times, so I added a Timer control, to run the Application.Doevents only two times by second.

The problem is:
If I write the .Join() line, then the timer don't Tick. (Private Sub TimerDoEvents_Tick never runs).

But if I add the MSGBOX("") line before the .Join() line, then ¡the timer Ticks! (ticks only until the messagebox is closed); Private Sub TimerDoEvents_Tick runs wonderfully two times a second, and the progressBar is updated. Anybody is happy.

Also the timer ticks if I right click the application button in Windows Taskbar opening the context menu.

What is going on? ¿Some hint?
Guillermo.Marraco  Friday, December 12, 2008 11:39 AM
If what you are trying to do is give feedback for your thread you need to start the thread and the Timer at the same time. In the Timers Tick event check to see if the thread is still working. If it is update the progress bar if not stop the timer. You don't need the DoEvents any where because the "Heavy Sub" is not running on the same thread as the form Main is using.

Checkout this Example:

ImportsSystem
ImportsSystem.Threading
PublicClassForm1
PrivatemyProgAsNewProgressBar
PrivatethrdTimerAsNewWindows.Forms.Timer
PrivatemyThreadAsThread
PrivatemThreadProgressAsInteger
PublicPropertyThreadProgress()AsInteger
Get
ReturnmThreadProgress
EndGet
Set(ByValvalueAsInteger)
mThreadProgress=value
EndSet
EndProperty
PrivateSubForm1_Load(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlesMe.Load
'addprogressbarandtimersoexampleworks
WithmyProg
.Name="MyProg"
.Maximum=100
.Minimum=0
.Dock=DockStyle.Bottom
EndWith
Me.Controls.Add(myProg)
AddHandlerthrdTimer.Tick,AddressOfThreadTimer_Tick
EndSub
PrivateSubForm1_Click(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlesMe.Click
'makesureMyThreadisn'talreadyworking
IfmyThreadIsNotNothingAndAlsomyThread.IsAliveThen
ExitSub
EndIf
'Createthreadandstartit
myThread=NewThread(AddressOfThreadProc)
WithmyThread
.IsBackground=True
.Start()
EndWith
'StartTimer
WiththrdTimer
.Interval=500
.Enabled=True
EndWith
EndSub
PrivateSubThreadTimer_Tick(ByValsenderAsObject,ByValeAsSystem.EventArgs)
DimTheTimerAsWindows.Forms.Timer=CType(sender,Windows.Forms.Timer)
'ChecktoseeifThreadisfinished;Clearvaluesandstoptimerifitis
IfmyThreadIsNothingOrElsemyThread.IsAlive=FalseThen
ThreadProgress=0
TheTimer.Enabled=False
EndIf
'UpdateProgress
myProg.Value=ThreadProgress
EndSub
PrivateSubThreadProc()
ForiAsInteger=1To100
'SetThreadProgress
ThreadProgress=i
'ZZzzzzz
Thread.Sleep(400)
Next
EndSub
EndClass

Hope this helps
TechNoHick  Friday, December 12, 2008 1:47 PM
I found the answers. It was so easy, I are ashamed:

WithmyThread
DoWhile.IsAlive
Thread.Sleep(RefreshInterval)
Application.DoEvents
Loop
.Join()
EndWith

The Sleep avoids wasting CPU cycles.
Probably even the timer is unnecessary.

Thanks for your efforts to help.

(Still, I don't understand why the MessageBox allows the timer to tick. It should exist a cleaner solution)
Guillermo.Marraco  Friday, December 12, 2008 4:56 PM
If what you are trying to do is give feedback for your thread you need to start the thread and the Timer at the same time. In the Timers Tick event check to see if the thread is still working. If it is update the progress bar if not stop the timer. You don't need the DoEvents any where because the "Heavy Sub" is not running on the same thread as the form Main is using.

Checkout this Example:

ImportsSystem
ImportsSystem.Threading
PublicClassForm1
PrivatemyProgAsNewProgressBar
PrivatethrdTimerAsNewWindows.Forms.Timer
PrivatemyThreadAsThread
PrivatemThreadProgressAsInteger
PublicPropertyThreadProgress()AsInteger
Get
ReturnmThreadProgress
EndGet
Set(ByValvalueAsInteger)
mThreadProgress=value
EndSet
EndProperty
PrivateSubForm1_Load(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlesMe.Load
'addprogressbarandtimersoexampleworks
WithmyProg
.Name="MyProg"
.Maximum=100
.Minimum=0
.Dock=DockStyle.Bottom
EndWith
Me.Controls.Add(myProg)
AddHandlerthrdTimer.Tick,AddressOfThreadTimer_Tick
EndSub
PrivateSubForm1_Click(ByValsenderAsObject,ByValeAsSystem.EventArgs)HandlesMe.Click
'makesureMyThreadisn'talreadyworking
IfmyThreadIsNotNothingAndAlsomyThread.IsAliveThen
ExitSub
EndIf
'Createthreadandstartit
myThread=NewThread(AddressOfThreadProc)
WithmyThread
.IsBackground=True
.Start()
EndWith
'StartTimer
WiththrdTimer
.Interval=500
.Enabled=True
EndWith
EndSub
PrivateSubThreadTimer_Tick(ByValsenderAsObject,ByValeAsSystem.EventArgs)
DimTheTimerAsWindows.Forms.Timer=CType(sender,Windows.Forms.Timer)
'ChecktoseeifThreadisfinished;Clearvaluesandstoptimerifitis
IfmyThreadIsNothingOrElsemyThread.IsAlive=FalseThen
ThreadProgress=0
TheTimer.Enabled=False
EndIf
'UpdateProgress
myProg.Value=ThreadProgress
EndSub
PrivateSubThreadProc()
ForiAsInteger=1To100
'SetThreadProgress
ThreadProgress=i
'ZZzzzzz
Thread.Sleep(400)
Next
EndSub
EndClass

Hope this helps
TechNoHick  Friday, December 12, 2008 1:47 PM
why not use background worker and the report progress feature?
dbasnett  Friday, December 12, 2008 2:33 PM
In answer to TechNoHic:
[If what you are trying to do is give feedback for your thread you need to start the thread and the Timer at the same time. In the Timers Tick event check to see if the thread is still working. If it is update the progress bar if not stop the timer. You don't need the DoEvents any where because the "Heavy Sub" is not running on the same thread as the form Main is using.

Checkout this Example:]
The problem is, that I need to wait for the thead to end.

If I write

myThread.Join()

after starting the timer, then the timer stops Ticking.

If instead of .Join() I write
WithmyThread
DoWhile.IsAlive
MsgBox("")
Loop
EndWith

then it works!!... although is a bad idea.

I can do then:
WithmyThread
DoWhile.IsAlive
Application.DoEvents
Loop
EndWith
It also works, but eat an entire processor.
Guillermo.Marraco  Friday, December 12, 2008 2:57 PM
dbasnett said:

why not use background worker and the report progress feature?


because I would need to add a timer also in the backgroudWorker, to report the progress only two times by second.
Guillermo.Marraco  Friday, December 12, 2008 3:02 PM
Why are you using Join? You create a thread, then join it. Defeats the purpose, I'd say.

You don't need a second timer: your main timer is already updating the progress bar with the Progress (and you don't need DoEvents, either.

The question is, what do you mean by you 'need to wait for the thread to end'? Can't you check this in your timer routine? In fact, TechNoHick's code does exactly what you want...so it's not clear what your expectations are.




Stephen J Whiteley
SJWhiteley  Friday, December 12, 2008 3:09 PM
Yes what SJW said; you don't need to join any threads. You don't check the status of the Thread from within itself you check the status in the Timer's Tick event. If you start a new project or form and paste my example into it's code you will see what I mean

You Start a Thread and a Timer
The Timer checks how the Thread is doing at whatever interval you set (1/2 sec in my example)
If the Thread is finished then the Timer cleans up and Stops
If the Thread is still working then the Timer updates the progress bar
TechNoHick  Friday, December 12, 2008 3:50 PM
SJWhiteley said:

Why are you using Join? You create a thread, then join it. Defeats the purpose, I'd say.

You don't need a second timer: your main timer is already updating the progress bar with the Progress (and you don't need DoEvents, either.

The question is, what do you mean by you 'need to wait for the thread to end'? Can't you check this in your timer routine? In fact, TechNoHick's code does exactly what you want...so it's not clear what your expectations are.




Stephen J Whiteley

The basic idea was to take out [of the LoopPrincipal ] the code which updates the progress bar.

LoopPrincipal only updates the variable ProgressBarValue.
The timer (in another thread) updates the ProgressBarArchivos two times by second, with the value stored by LoopPrincipal in ProgressBarValue.

I cannot continue running my code until LoopPrincipal ends, because I need his resulting data, and I has read that to wait for the thread to end, you need to use .Join()

The problem is, if I use Join, my timer stop ticking, but if I start a MsgBox(""), then the timer ticks, and the ProgressBar updates, as expected. But the messagebox is not acceptable solution, because requires user intervention (And is bad coding!).


Guillermo.Marraco  Friday, December 12, 2008 3:51 PM
If you need the Data before you can continue place the next steps in a Sub or Function when the Timer detects that the Thread is finished then it would call said Sub/Function I.E

PrivateSubThreadTimer_Tick(ByValsenderAsObject,ByValeAsSystem.EventArgs)
DimTheTimerAsWindows.Forms.Timer=CType(sender,Windows.Forms.Timer)
'ChecktoseeifThreadisfinished;Clearvaluesandstoptimerifitis
IfmyThreadIsNothingOrElsemyThread.IsAlive=FalseThen
ThreadProgress=0
TheTimer.Enabled=False
DoMoreWork()
EndIf
'UpdateProgress
myProg.Value=ThreadProgress
EndSub
PrivateSunDoMoreWork()
'FinishProcess
EndSub


TechNoHick  Friday, December 12, 2008 3:59 PM
I found the answers. It was so easy, I are ashamed:

WithmyThread
DoWhile.IsAlive
Thread.Sleep(RefreshInterval)
Application.DoEvents
Loop
.Join()
EndWith

The Sleep avoids wasting CPU cycles.
Probably even the timer is unnecessary.

Thanks for your efforts to help.

(Still, I don't understand why the MessageBox allows the timer to tick. It should exist a cleaner solution)
Guillermo.Marraco  Friday, December 12, 2008 4:56 PM
Guillermo Marraco said:

I found the answers. It was so easy, I are ashamed:

WithmyThread
DoWhile.IsAlive
Thread.Sleep(RefreshInterval)
Application.DoEvents
Loop
.Join()
EndWith

The Sleep avoids wasting CPU cycles.
Probably even the timer is unnecessary.

Thanks for your efforts to help.

(Still, I don't understand why the MessageBox allows the timer to tick. It should exist a cleaner solution)

This is not your solution (or any solution) and is an example of poor coding. Why do you use the join method here? If the thread isn't alive, there's nothing to join for one thing.

If you want to use it, then fine. But anyone else who is running into similar issues should use the code posted by Technohick - a cleaner solution - and not this.

Look at TechNoHick's code again - you'll see that you can run specific code when the thread completes; see that the timer is disabled/stopped on thread completion, so you 'know' when data is available.



Stephen J Whiteley
SJWhiteley  Friday, December 12, 2008 7:01 PM
if you used background worker and report progress, and you only wanted updates every 1/2 second you could

DimaStpwAsNewStopwatch
aStpw.Start()
IfaStpw.ElapsedMilliseconds>=500Then
'reportprogress
aStpw.Reset():aStpw.Start()
EndIf

dbasnett  Friday, December 12, 2008 7:34 PM
SJWhiteley said:

This is not your solution (or any solution) and is an example of poor coding. Why do you use the join method here? If the thread isn't alive, there's nothing to join for one thing.

If you want to use it, then fine. But anyone else who is running into similar issues should use the code posted by Technohick - a cleaner solution - and not this.

Look at TechNoHick's code again - you'll see that you can run specific code when the thread completes; see that the timer is disabled/stopped on thread completion, so you 'know' when data is available.



Stephen J Whiteley

Thanks! I did not see it. Of course, if the line is executed, then the thread is already ended.

Its weird than it not raised an error when I called the .Join() line on the ended thread.
____________

The Technohic solution is valuable, and I appreciate him, but forces you to divide the algorithm in two subs; the first part of the algorithm call this class, and then you need to call the next class, who after ending needs to return the control to the original class. It could cause maintainance headaches, because you need to keep track of who called your code.
____________

I no more use the Timer

I call the update progressBar this way:
WithmyThread
DoWhile.IsAlive
Thread.Sleep(RefreshInterval)
MAIN_Form.ProgressBarArchivos.Value=Interlocked.Read(ThisClass.ProgressBarValue)
Application.DoEvents()
Loop
EndWith

If it still is bad coding, I would thank you for telling me.
Guillermo.Marraco  Friday, December 12, 2008 7:55 PM
dbasnett said:

if you used background worker and report progress, and you only wanted updates every 1/2 second you could

DimaStpwAsNewStopwatch
aStpw.Start()
IfaStpw.ElapsedMilliseconds>=500Then
'reportprogress
aStpw.Reset():aStpw.Start()
EndIf


As I understand, (I never worked with backgroundWorkers. I'm a .NET newbie) in the background worker, you need to write something like:

Public ProgressBarValue As long
ForEachElementAsSomethingInVeryLargeArray
'Dosomething
ProgressBarValue+=1
If(ProgressBarValueasincreasedinsomehundreds)then
'updatebackwroundWorkerprogress
EndIf
NextElement

But I wanted to remove the If (ProgressBarValueasincreasedinsomehundreds) evaluation, because is run millions of times.

Removing it was my goal.
So I planned to move it to another thread.

PD: Excuse me by the font format. It does not work. I can't choose the fonts. The controls are not working.
Guillermo.Marraco  Friday, December 12, 2008 8:07 PM
Public ProgressBarValue As long
DimaStpwAsNewStopwatch
aStpw.Start()
ForEachElementAsSomethingInVeryLargeArray
'Dosomething
ProgressBarValue+=1
IfaStpw.ElapsedMilliseconds>=500Then
'reportprogress
aStpw.Reset():aStpw.Start()
EndIf
NextElement


dbasnett  Saturday, December 13, 2008 2:00 PM

You can use google to search for other answers

Custom Search

More Threads

• About the Cross-thread problem ?
• Another thing that I can't figure out...
• Do I need a class
• How do I manually position the cursor to the end of the text in a text box?
• Control handles
• How to make a Timer and ButtonVisibility Question.
• Get load status of web browser.
• How to fix this inserting new line in RichTextBox?
• Have VBA, C experience but would it be good enough for VB.net jobs
• how to Convert encoding from windows 1256 to asmo 708