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 - Marked As Answer bySJWhiteleyModeratorFriday, December 12, 2008 7:02 PM
-
| | 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) - Marked As Answer byGuillermo.Marraco Friday, December 12, 2008 5:23 PM
-
| | 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 - Marked As Answer bySJWhiteleyModeratorFriday, December 12, 2008 7:02 PM
-
| | 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 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 | 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 | 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) - Marked As Answer byGuillermo.Marraco Friday, December 12, 2008 5:23 PM
-
| | Guillermo.Marraco Friday, December 12, 2008 4:56 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) 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 | 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 | 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 |
|