A lot of people ask how to add a pause to their code, i.e. execute some code, wait for a period of time and then execute some more code. The first thing that comes to mind is to call Thread.Sleep. It does exactly that. The problem is that it blocks the current thread so, if you call it on the main thread of a GUI app, it freezes your UI. A lot of people attempt or suggest to use a "busy wait loop" to prevent that freeze, which involves a Do or While loop, within which you call Application.DoEvents to process any pending user input or UI repaints. That is an abominable solution because it will cause your processor to run at 100% for the duration of the loop. That's why it's called "busy waiting": because "wait" implies that the code is doing nothing but it's actually busy working to maximum capacity.
The proper approach is to find a way to make your code actually do nothing but be able to do something if required. Here's a module I created that does just that:
vb.net Code:
Imports System.Threading
''' <summary>
''' Provides functionality to execute a method after a pause.
''' </summary>
Public Module Pauser
#Region " Types "
''' <summary>
''' Contains information about how to invoke a method.
''' </summary>
Private Class InvocationInfo
''' <summary>
''' The method to invoke.
''' </summary>
Public method As [Delegate]
''' <summary>
''' The arguments to pass to the method.
''' </summary>
Public arguments As Object()
''' <summary>
''' The context in which to invoke the method.
''' </summary>
Public context As SynchronizationContext
End Class
#End Region 'Types
#Region " Fields "
''' <summary>
''' Contains information about methods to invoke keyed on the <see cref="Timers.Timer">Timer</see> used to delay their invocation.
''' </summary>
Private ReadOnly invocationInfoByTimer As New Dictionary(Of Timers.Timer, InvocationInfo)
#End Region 'Fields
#Region " Event Handlers "
''' <summary>
''' Handles the <see cref="Timers.Timer.Elapsed">Elapsed</see> event of the <see cref="Timers.Timer">Timer</see> used to deley the invocation of a method.
''' </summary>
''' <param name="sender">
''' The <b>Timer</b> that has elapsed.
''' </param>
''' <param name="e">
''' The data for the event.
''' </param>
Private Sub Timer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs)
Dim timer = DirectCast(sender, Timers.Timer)
'Get the method information associated with the Timer.
Dim info = invocationInfoByTimer(timer)
'The Timer is no longer needed.
invocationInfoByTimer.Remove(timer)
timer.Dispose()
If info.context Is Nothing Then
'This is not a Windows GUI app so execute the method on the current thread.
Execute(info)
Else
'This is a Windows GUI app so execute the method on the original thread.
info.context.Post(AddressOf Execute, info)
End If
End Sub
#End Region 'Event Handlers
#Region " Methods "
''' <summary>
''' Executes a method after a pause.
''' </summary>
''' <param name="pause">
''' The period of time after which to execute the method.
''' </param>
''' <param name="method">
''' Refers to the method to execute.
''' </param>
''' <param name="arguments">
''' The arguments to pass to the method when executing it.
''' </param>
Public Sub ExecuteAfterPause(pause As Integer, method As [Delegate], ParamArray arguments As Object())
'Create a Timer to provide the delay that will raise its Elapsed event only once.
Dim timer As New Timers.Timer(pause) With {.AutoReset = False}
'Package up the information about the method to execute.
'The context is required so that the method is invoked on the same thread as we are executing on now.
Dim info As New InvocationInfo With {.method = method,
.arguments = arguments,
.context = SynchronizationContext.Current}
invocationInfoByTimer.Add(timer, info)
AddHandler timer.Elapsed, AddressOf Timer_Elapsed
timer.Start()
End Sub
''' <summary>
''' Executes the method.
''' </summary>
''' <param name="info">
''' Contains information about the method to invoke.
''' </param>
Private Sub Execute(info As Object)
With DirectCast(info, InvocationInfo)
.method.DynamicInvoke(.arguments)
End With
End Sub
#End Region 'Methods
End Module
Some may think that that's a lot of complex code to do something relatively simple. Well, the reason that things are simple is that Microsoft developers wrote a lot of complex code within the .NET Framework. They wrote it once and you can then invoke it very easily. That's the case with this code too. It's written once and then you can compile it into a DLL and use it very easily. Where you might then do this to create a pause that did freeze the UI:
vb.net Code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
DoSomething()
End Sub
Private Sub DoSomething()
MessageBox.Show(Date.Now.ToString())
Thread.Sleep(5000)
MessageBox.Show(Date.Now.ToString())
End Sub
you can do this to create a pause that doesn't freeze anything:
vb.net Code:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
StartSomething()
End Sub
Private Sub StartSomething()
MessageBox.Show(Date.Now.ToString())
ExecuteAfterPause(5000, New MethodInvoker(AddressOf FinishSomething))
End Sub
Private Sub FinishSomething()
MessageBox.Show(Date.Now.ToString())
End Sub
Note that the code that originally appeared after the pause in the same method now appears in its own method. That's all you need to do: simply break the code up into two methods.
Now, if the after-pause code needs some data then you can pass that via parameters of the second method. They get passed to the ExecuteAfterPause method as either a single Object array or, courtesy of the ParamArray, as individual arguments. Code that looks like this:
vb.net Code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
DoSomething(100, "John Smith")
End Sub
Private Sub DoSomething(count As Integer, name As String)
MessageBox.Show(Date.Now.ToString())
Thread.Sleep(5000)
MessageBox.Show("Count: " & count, name)
End Sub
could become this:
vb.net Code:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
StartSomething(100, "John Smith")
End Sub
Private Sub StartSomething(count As Integer, name As String)
MessageBox.Show(Date.Now.ToString())
ExecuteAfterPause(5000,
New Action(Of Integer, String)(AddressOf FinishSomething),
New Object() {count, name})
End Sub
Private Sub FinishSomething(count As Integer, name As String)
MessageBox.Show("Count: " & count, name)
End Sub
or this:
vb.net Code:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
StartSomething(100, "John Smith")
End Sub
Private Sub StartSomething(count As Integer, name As String)
MessageBox.Show(Date.Now.ToString())
ExecuteAfterPause(5000,
New Action(Of Integer, String)(AddressOf FinishSomething),
count,
name)
End Sub
Private Sub FinishSomething(count As Integer, name As String)
MessageBox.Show("Count: " & count, name)
End Sub
Last edited by jmcilhinney; Apr 14th, 2013 at 03:35 AM.
Reason: Added module code file
This works great if we know in advance the time duration we want to pause the code, and the parts of the code which can be broken apart.
I am wondering if this code can be modified so that the pause can be user-controlled?
e.g. Say I have a PauseButton and ContinueButton on the form that pauses and unpauses some long running task?
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
This works great if we know in advance the time duration we want to pause the code, and the parts of the code which can be broken apart.
I am wondering if this code can be modified so that the pause can be user-controlled?
e.g. Say I have a PauseButton and ContinueButton on the form that pauses and unpauses some long running task?
No. You can't just arbitrarily pause code. If you want to be able to pause a long-running task then you will have to put waypoints in it. Consider how a background task is cancelled using the BackgroundWorker. The user requests cancellation and that request is recorded but it doesn't actually cancel the task. You have to write code to intermittently check whether cancellation has been requested and then you can end the task. You would need to do something similar for pausing. That would be easiest if you were doing something in a loop and then you can test each iteration. You could then pause in, say, 1 second blocks and keep doing that until you detect that the user requested to continue.
No. You can't just arbitrarily pause code. If you want to be able to pause a long-running task then you will have to put waypoints in it. Consider how a background task is cancelled using the BackgroundWorker. The user requests cancellation and that request is recorded but it doesn't actually cancel the task. You have to write code to intermittently check whether cancellation has been requested and then you can end the task. You would need to do something similar for pausing. That would be easiest if you were doing something in a loop and then you can test each iteration. You could then pause in, say, 1 second blocks and keep doing that until you detect that the user requested to continue.
Think that was what .Suspend and .Resume were for ... but MS depreciated them without a replacement ... to my knowledge anyway
vb Code:
Dim t As New System.Threading.Thread(AddressOf TestLoop)
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
t.Suspend()
End Sub
Private Sub TestLoop()
Do
Static i As Integer
i += 1
Me.Invoke(Function() SetFormText(i.ToString))
System.Threading.Thread.Sleep(1000)
Loop
End Sub
Private Function SetFormText(ByVal str As String) As Boolean
Me.Text = str
Return True
End Function
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Button1.Text = "Pause"
Button2.Text = "Resume"
t.Start()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
MS depreciated them without a replacement ... to my knowledge anyway
They didn't take that decision arbitrarily. Suspending a thread without any knowledge of where it's at is bad. You are supposed to use proper thread synchronisation techniques in their place. You can still use them if you want as they still exist right up to .NET 4.5 and may never be removed altogether. They may well not cause any problems at all in certain cases. They may cause problems in some cases though so they should not be used. It's never a good idea to use a method flagged as obsolete.
They didn't take that decision arbitrarily. Suspending a thread without any knowledge of where it's at is bad. You are supposed to use proper thread synchronisation techniques in their place. You can still use them if you want as they still exist right up to .NET 4.5 and may never be removed altogether. They may well not cause any problems at all in certain cases. They may cause problems in some cases though so they should not be used. It's never a good idea to use a method flagged as obsolete.
I know this ... but in certain cases IT DOESN'T MATTER ... they should have left the option in for cases where it doesn't ... rather than deciding "they know best" (sounds like an apple move)
Anyway I guess you could use a wait handle for eg:
vb Code:
Dim TriggerPaused As Boolean
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
TriggerPaused = True
End Sub
Dim wh As New System.Threading.EventWaitHandle(False, Threading.EventResetMode.AutoReset)
Private Sub TestLoop()
Do
Static i As Integer
i += 1
Me.Invoke(Function() SetFormText(i.ToString))
System.Threading.Thread.Sleep(1000)
If TriggerPaused = True Then
TriggerPaused = False
wh.WaitOne(Integer.MaxValue)
End If
Loop
End Sub
Private Function SetFormText(ByVal str As String) As Boolean
Me.Text = str
Return True
End Function
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Button1.Text = "Pause"
Button2.Text = "Resume"
Dim t As New System.Threading.Thread(AddressOf TestLoop) With {.IsBackground = True}
t.Start()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
they should have left the option in for cases where it doesn't ... rather than deciding "they know best" (sounds like an apple move)
And you know exactly which cases it matters in and which it doesn't, right? And you're going to prevent those who don't from using it in cases where they shouldn't, right? I think that it's a fair bet that Microsoft do know the inner workings of the .NET Framework best. They obviously had a reason for deprecating those methods and it was most likely because they got all sorts of questions about why multi-threaded code wasn't working properly. You think it would have been best to just allow those cases to continue? Basically, if you're too lazy to do it properly then just don't do it at all.
No. You can't just arbitrarily pause code. If you want to be able to pause a long-running task then you will have to put waypoints in it. Consider how a background task is cancelled using the BackgroundWorker. The user requests cancellation and that request is recorded but it doesn't actually cancel the task. You have to write code to intermittently check whether cancellation has been requested and then you can end the task. You would need to do something similar for pausing. That would be easiest if you were doing something in a loop and then you can test each iteration. You could then pause in, say, 1 second blocks and keep doing that until you detect that the user requested to continue.
Pausing and resuming on secondary threads is easy. You can keep a handle of the thread and then easily check and put Sleep at appropriate place in the loop, or even pause/resume the thread etc.
I was actually talking about pausing task on the UI thread, where putting Sleep on the thread doesn't work good. Somewhat similar to what you showed in your post.
Pradeep, Microsoft MVP (Visual Basic) Please appreciate posts that have helped you by clicking icon on the left of the post.
"A problem well stated is a problem half solved." — Charles F. Kettering
I just wanted to let you know that I'm going to be using this as my managed game loop. It's absolutely perfect and removes the long time assumption that you need DoEvents in a game loop. To give you an idea of what this does to my games, it drops the Memory(in task manager) from about 150,000 k to 6,000 or 7,000!
I just wanted to let you know that I'm going to be using this as my managed game loop. It's absolutely perfect and removes the long time assumption that you need DoEvents in a game loop. To give you an idea of what this does to my games, it drops the Memory(in task manager) from about 150,000 k to 6,000 or 7,000!
I'm not a game developer myself so I'd not envisioned it being used in that way specifically but I'm glad that you've found it useful.
Is it possible that we can continue in same point after a pause instead of breaking up functions?
Like.
Line 1 - codes..
Line 2 - codes..
Line 3 - codes..
Line 4 - ExecuteWait(5000)
Line 5 - After 5 Secs, it should come here.
Line 6 - codes..
Is it Possible?
It occurred to me that it must be possible to do something like the above using the Await operator, which together with Async and Tasks has been around since about 2012. I haven't tried it before, but after a bit of tinkering it seems to be working as intended.
To try the example code below, start with a form with 2 buttons (Button1 and Button2) and a text box (TextBox1).
The Button1 click code sets the text box text to "Waiting...", waits for 5 seconds, then changes it to "Ready". Note that you must add the Async keyword to the Button1.Click sub.
While waiting, you can click Button2 to change the TextBox background colour between blue and yellow over and over again while waiting. This shows that the UI thread isn't being held up.
Under the hood, the method actually uses Threading.Thread.Sleep to create the delay. But it's only the asynchronous task which goes to sleep, so it doesn't interfere with anything else.
Here's the code for the form:
Code:
Public Class Form1
'This is an example of how to use the Delay function:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TextBox1.Text = "Waiting..."
delayMilliseconds = 5000
Await Delay()
TextBox1.Text = "Ready!"
End Sub
'This is the code that implements the Delay function:
Private delayMilliseconds As Integer
Private Async Function Delay() As Task
Dim task As New Task(AddressOf DoSleep)
task.Start()
Await task
End Function
Private Sub DoSleep()
Threading.Thread.Sleep(delayMilliseconds)
End Sub
'This sub is just to demonstrate that the UI is still active during the delay.
'Click button 2 to change the text box background.
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Static b As Boolean = False
b = Not b
If b Then TextBox1.BackColor = Color.SkyBlue Else TextBox1.BackColor = Color.Yellow
End Sub
End Class
I'm sure it must be possible to do something similar with threads, but the Await syntax is simpler and clearer in my view.
BB
Last edited by boops boops; Aug 1st, 2016 at 05:09 AM.