Constantly updating "Status Form" locks over time - vba

This is my first time using VBA in Outlook so please bear with me
I've created a basic Macro that does various things to folders. Since this takes a while I decided to make a status window that says what its currently doing. I simply keep setting one of the label's values with this
Function UpdateStatus(Message As String)
StatusForm.StatusUpdate.Caption = Message
StatusForm.Repaint
End Function
The issue is that after it runs for a bit (5-15 seconds) the window and the rest of outlook locks; the form no longer updates and has a "(Not Responding)" in its window title.
I feel like I'm somehow dead locking the UI thread but I'm at a loss on how to work around it. Commenting out Repaint not surprisingly doesn't let it update at all, but outside of that I don't know where to look
Any suggestions?

Try adding DoEvents in your computationally intensive loop. This yields the executing code to the UI thread so that other things can get done when you have computationally intensive stuff going on in the background. Office is single-threaded, so you can block the UI when you are running macros.
Sample:
Sub LockUI()
Dim x
x = Timer
Do While Timer - x < 5
'Blocks the UI for 5 seconds
Loop
End Sub
Sub LockUI2()
Dim x
x = Timer
Do While Timer - x < 5
'Doesn't block the UI
DoEvents
Loop
End Sub

Related

Code Execution Sequence

I am a PLC programmer who is currently using a variant of VB to control a motor.
I want to call a function that will execute moves and not return to the main code until the move has been completed. Currently here is what I have:
Program 'Main Program
While 1
If move_req = 1
Function MoveMotor
End If
Wend
End Program
Function MoveMotor
MoveABS 10 ' Move to encoder position 10mm
move_complete = 1
While move_req = 1
'Do Nothing
Wend
End Function
For some reason this code isn't working and the move command is being sent over and over again. Could this be because the main program continues to run when the function is running? Is that how VB works? I am used to thinking of code sequence in terms of PLC's where they scan through everything repeatedly at a certain frequency.
Whenever the move is complete, there must be a way for it to be detected by the program. It looks like you want move_req to be set to zero when this happens, but I cannot see what would cause that. How does the machine signal the program that it's finished moving?
A second point is that when you have a loop that waits while it checks for variable change, it can cause a CPU spike. You can put a pause in the loop with some thing like System.Threading.Thread.Sleep(100) where 100 is milliseconds to pause.

.NET Equivalent for VB6 DoEvents + Sleep

I have a program that I'm updating from VB6 to VB.NET that is used for making optical measurements. The hardware runs Win XP or Win 2000 Embedded. In the original code, there is a section that is triggered after a measurement is started that uses DoEvents and Sleep:
While instDefItf.InstrumentState = Busy
Sleep 500
DoEvents
Wend
I've tried replacing the middle two lines in my code with a simple Threading.Thread.Sleep(500) (I realize that this is probably poor practice, but not having a ton of experience with VB, I was trying to preserve the VB6 program as closely as possible). This makes the code work correctly - on the second try. On the first try, the measurements that are supposed to be taken simply aren't, but no errors are thrown and the subsequent code executes correctly.
Based on this, I have two primary questions. First, is there something I could simply replace the VB6 code with to get the desired functionality? Second, is there a better way to periodically query the instrument state that doesn't make use of the Sleep function?
EDIT:
The function I referred to above is a helper function (I think that's the right term) that checks to see if the instrument has completed its measurement. The measurement routine is contained within a DLL, and the code that executes it is given below.
If cbMeasLength.Value = vbUnchecked Then
Call I12001Ref.StartMtj1IlAcquisition2(CLng(lChannel), ConnectorA, m_moduleCollection.ReflectanceModule(lRm).SerialNumber)
End If
' Wait the measurement completion
If cbMeasLength.Value = vbUnchecked Then
If WaitForOperationCompleted = False Then
Exit Sub
End If
End If

Threading. How does button click interrupt/influence program flow

My project is vb.net 2010 windows desktop form.
So far, single threaded (default).
If a SUBroutine has a for...next loop in it that is running, what happens if a buttonclick event is fired and within that event a variable is changed? Like: does program execution leave the loop that was running? Or does it continue to run while that variable is changed by the buttonclick event?
What I'm aiming for:
If someone clicks the button, blnRequestStop is set to True.
Within that for...next loop, just before the "next" it checks blnRequestStop. If true then it will exit the "for" loop.
I'm guessing I need to use threads? Can anyone give me a simple example, please?
EDIT:
This code below seems to be working fine. But maybe you all see a problem?
If (btnProcess.Text = "Done!") Then
End
ElseIf (btnProcess.Text = "IMPORT") Then
bRequestStop = False
t1 = New Thread(AddressOf ProcessDo)
t1.Start()
Else
t2 = New Thread(AddressOf MyInterrupt)
t2.Start()
End If
Here is the short version of what ProcessDo and MyInterrupt do:
Private Sub ProcessDo()
For each X in blahblah
'do stuff (yes, includes interface)
if (blnInterrupt) then exit For
Next X
End
End Sub
Private Sub MyInterrupt()
blnInterrupt=true
End Sub
Yes, you probably want to do the long-running task on a background thread. Here's a code sample including how you'd get the results back to the UI thread when you're done (otherwise you'll get errors about Cross-thread operation not valid).
ThreadPool is a nice way to do some work on a background thread. You could set stopIt = True in the button click event for the stop button.
ThreadPool.QueueUserWorkItem(
Sub()
For Each thing In things
If stopIt Then Exit Sub
'Do the stuff!
Next
'We're done, update UI
Me.UpdateUI("All done!")
End Sub)
To safely update the UI, you'll need to make sure you get back to the UI thread.
Public Sub UpdateUI(result As String)
If Me.InvokeRequired() Then
'If we aren't on the UI thread, invoke this function on the UI thread
Me.BeginInvoke(Sub() UpdateUI(result))
Exit Sub
End If
'Update UI here
lblResult.Text = result
End Sub
Execution is 'stopped' (in a way) until the Loop finishes him job, so yes, you need to multi-thread.
It it is not a very long operation where you need to update UI controls during the Loop then you just can use Application.DoEvents inside the loop to be able to use other controls as normally in the application when FOR loop is working, but I advise you that this will have a negative impact on UI performance, but if it's not a long duration loop then you maybe would consider to use DoEvents instead introduce into multi-threading it's just an alternative, not recommended but, you can use it.
PS: Forgive my English

.net 2010 calling DoEvents (yes I want to) from inside a custom control library

I have a custom control's library. Now there's a control which looks like a panel, and when it opens up I want to animate its vertical growing like this:
For h As Single = 0 To finalHeight Step 0.5
Me.Height = CInt(h)
' HERE I WANT TO CALL DoEvents'
Next
Me.Height = finalHeight
If I don't call DoEvents in the loop then the animation is not shown, I only get the final height without a visual feedback along the way.
I can call DoEvents from inside my main WinForm project, but can't inside a library.
How can I do that, without drowning into the deep threads waters?
Sorry, but it is completely impossible to make using DoEvents safe here. Nothing good is going to happen when the user closes the form while your animation is going. It will crash the program with an ObjectDisposed exception. Making DoEvents safe requires setting the form's Enabled property to false so that the user cannot accidentally cause mishaps like this. A control can not reasonable set the form's Enabled property to false, especially not for an animation.
The workaround is simple enough, just use a Timer with an Interval of 15 msec. Plenty fast enough to make the animation look smooth. You'll find sample code that does this in my answer in this thread.
Maybe you are just missing a reference to (or import of) System.Windows.Forms? DoEvents is a static method of Application, so you should be able to call it from a library as well.
Imports System.Windows.Forms
...
Application.DoEvents()
(You already seem to know that using DoEvents is a dangerous thing, so I'll skip the usual lecture here.)
Yes, you should be able to call
System.Windows.Forms.Application.DoEvents()
From within your code library. It seems that you understand that DoEvents is a Bad Idea, so I'm not sure why you're calling it. I'm guessing that you have this put inside an override like OnVisibleChanged, or OnPaint - if this is the case you will most likely not get the results you are after, as control refreshing will be suspended during these operations.
What you probably want to do is create a single-tick timer, and on tick increase the height of the control -then disable the timer when finalheight is reached, or schedule another tick if not. Or, create a timer and put your above loop in it on each tick. Make sure you're aware of InvokeRequired and cross-thread calls depending on what type of timer you use.
Failure to reproduce.
Just tested with a simple setup, UserControl in an assembly.
A timer on the MainForm keeps ticking when the UC calls DoEvents() in a loop.
So: Look again for your problem, it isn't where you think it is.
In your original for loop, place Me.Refresh where you wanted to call the doevents.
For h As Single = 0 To finalHeight Step 0.5
Me.Height = CInt(h)
Me.refresh
Next
Me.Height = finalHeight
This is what I've found: the timer, even at fast intervals, it is really slow. I don't know why but the animation is very jumpy with the timer. Simplified code:
rolex = New Timer()
rolex.Interval = 150
AddHandler rolex.Tick,
Sub(sender As Object, e As EventArgs)
Me.Height += 5
If Me.Height < finalHeight Then Exit Sub
rolex.Stop()
rolex = Nothing
Me.Height = finalHeight
End Sub
rolex.Start()
Without the timer I use a loop:
For i As Single = 0 To finalHeight Step 0.5
Height = CInt(i)
Application.DoEvents()
Next
Height = finalHeight
It works now, but the problem is that the animation speed too much depends on the machine where the loop is executed. For this reason I'd like to use a Timer, but as I said it's too slow.
Any hints?

How to limit CPU usage in a while loop

How do you limit the CPU of a while loop?
In this case, the code which is inside the while loop:
Private Sub wait(ByVal time)
Dim sw As New Stopwatch
sw.Start()
Do While sw.ElapsedMilliseconds < time And StillOpen = True
Application.DoEvents()
Loop
sw.Stop()
End Sub
But now, here is the issue. This loop is allowing the while loop to run every second, once a second, and the wait sub is causing this delay, as it should.
How can I limit the CPU that this is taking up? For some reason, my task manager says it is taking 50 CPUs to run this simple task, yet it should probably take no more than 1 or 2. Though the manager says it is taking that much CPU, my computer speed is not being affected at all, which is odd considering it is a two-year-old laptop.
I don't want any users to freak out about it, but knowing how people are these days....
Anyway, the language is vb.net. Can someone please help me?
Thanks!
EDIT: To clarify, that code is not inside the while loop itself, but a call for the subroutine is, i.e. wait(1000)
Use a timer event !!! Nearly no cpu effort.
You could always perform some kind of sleep between iterations of the loop...
I'm not familiar with VB.NET but a duration of 100-200ms will probably be more than enough to drop the CPU usage.
Eg:
Do while (...)
Application.blah();
System.Threading.Thread.Sleep(150);
End
Edit After some research, I think the function you want is: System.Threading.Thread.Sleep()
Your code is executing Application.DoEvents() constantly in the while loop, for the time duration specified in your time parameter. This will consume one core of your CPU, which is why you're seeing 50% processor usage (you have a dual-core processor, correct?). This is an ugly way to wait. You could instead call Thread.Sleep(), passing it the number of milliseconds you'd like your thread to wait.
If you'd like your application to stay responsive, you might also spin off a timer, and block the UI from any action until the timer triggers. Something like (lightly tested):
// constructor or designer code
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Tick += new EventHandler(timer_Tick);
void Wait(int interval)
{
timer.Interval = interval;
timer.Start();
BlockUIOperations(); // implement yourself
}
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
EnableUIOperations(); // implement yourself
}
Here's my attempt at a translation into VB:
'' Add a Timer object to the form named "Timer".
'' Hook its Tick event to Timer_Tick
Private Sub Wait(ByVal interval As Integer)
Timer.Interval = interval
Timer.Start()
BlockUIOperations() '' implement yourself
End Sub
Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer.Tick
Timer.Stop()
EnableUIOperations() '' implement yourself
End Sub
Well, the CPU is always running at 100% when it's running, so the only practical way to limit the CPU usage is to run bursts or loop and sleeping in between.
Laptop CPUs usually have some SpeedStep technology or equievalent that will slow down the CPU when it's not working hard, but it's not reasonable to assume that your application would have access to control that, at least not directly. You might be able to affect it indirectly by measuring the CPU usage and adjust the length of the work and sleep cycles to get the desired result.
If you don't mind blocking the current thread, you could use a WaitHandle.
Public Sub Wait(ByVal ms As Integer)
Using wh As New ManualResetEvent(False)
wh.WaitOne(ms)
End Using
End Sub
Sub Main()
Console.WriteLine("Hello World!")
Wait(5000)
Console.WriteLine("Good-Bye!")
End Sub
Of course, something more complex can be constructed depending on what you are trying to accomplish.
This is perfect as a VB.net sleep replacement. Now my console app is NOT reported as non responsive since I have no sleep commands!
Just add Imports System.Threading above your module and place this just above your sub main
Public Sub Wait(ByVal ms As Integer)
Using wh As New ManualResetEvent(False)
wh.WaitOne(ms)
End Using
End Sub
Then, in your sub main, use
wait(100)
to pause your app for 100 miliseconds.
Have fun
You should take note of if you are doing this in the main UI Thread or a thread you have spun off.
For Threads the easiest way is to just Thread.Sleep(x miliseconds)
On the main UI thread I tend to use a DoEvents function in vb.net and vb6 like this
Public Sub TimeKiller(byval secondstowait as integer)
dim tmptime as datetime = datetime.now
do while datetime.now < dateadd("s",secondstowait,tmptime)
Application.Doevents
end while
End Sub
On the question of CPU usage I look at it like this.... if you make just a hard loop that like
while true
end while
I would expect to see very high cpu usage over 50% because the UI thread is hard blocking on this.... in most cases the windows system will limit the cpu usage of any given program so that its threads dont block the entire system.
The DoEvents ensure that windows message pumps fire correct and respond to correct. It also ensures that the garbage collector fires on time.
Also if you have other threads spun up off of your UI.Thread your UI.Thread can respond to events fired from these other threads....
In such cases where your calling form controls from other threads and do form.InvokeRequired routines will be able to respond correctly.
Also The only time you should be hard looping on the MainUI thread is when it is in response to some user activity and you need to put waits in for the user to see progress of something....
If it is some kind of automated process that is always running... look to moving it to another thread.
Or if its something that runs periodically on a timer or a time that kicks off a thread.
Somebody please tell me if I am wrong on these assumptions....
Not sure about the Using wh As New ManualResetEvent(False) wh.WaitOne(ms) as I have never heard of that and have no idea what that does.