Results 1 to 15 of 15

Thread: Adding a Pause to Your Code

  1. #1

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    104,961

    Adding a Pause to Your Code

    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:
    1. Imports System.Threading
    2.  
    3. ''' <summary>
    4. ''' Provides functionality to execute a method after a pause.
    5. ''' </summary>
    6. Public Module Pauser
    7.  
    8. #Region " Types "
    9.  
    10.     ''' <summary>
    11.     ''' Contains information about how to invoke a method.
    12.     ''' </summary>
    13.     Private Class InvocationInfo
    14.         ''' <summary>
    15.         ''' The method to invoke.
    16.         ''' </summary>
    17.         Public method As [Delegate]
    18.         ''' <summary>
    19.         ''' The arguments to pass to the method.
    20.         ''' </summary>
    21.         Public arguments As Object()
    22.         ''' <summary>
    23.         ''' The context in which to invoke the method.
    24.         ''' </summary>
    25.         Public context As SynchronizationContext
    26.     End Class
    27.  
    28. #End Region 'Types
    29.  
    30. #Region " Fields "
    31.  
    32.     ''' <summary>
    33.     ''' Contains information about methods to invoke keyed on the <see cref="Timers.Timer">Timer</see> used to delay their invocation.
    34.     ''' </summary>
    35.     Private ReadOnly invocationInfoByTimer As New Dictionary(Of Timers.Timer, InvocationInfo)
    36.  
    37. #End Region 'Fields
    38.  
    39. #Region " Event Handlers "
    40.  
    41.     ''' <summary>
    42.     ''' 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.
    43.     ''' </summary>
    44.     ''' <param name="sender">
    45.     ''' The <b>Timer</b> that has elapsed.
    46.     ''' </param>
    47.     ''' <param name="e">
    48.     ''' The data for the event.
    49.     ''' </param>
    50.     Private Sub Timer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs)
    51.         Dim timer = DirectCast(sender, Timers.Timer)
    52.  
    53.         'Get the method information associated with the Timer.
    54.         Dim info = invocationInfoByTimer(timer)
    55.  
    56.         'The Timer is no longer needed.
    57.         invocationInfoByTimer.Remove(timer)
    58.         timer.Dispose()
    59.  
    60.         If info.context Is Nothing Then
    61.             'This is not a Windows GUI app so execute the method on the current thread.
    62.             Execute(info)
    63.         Else
    64.             'This is a Windows GUI app so execute the method on the original thread.
    65.             info.context.Post(AddressOf Execute, info)
    66.         End If
    67.     End Sub
    68.  
    69. #End Region 'Event Handlers
    70.  
    71. #Region " Methods "
    72.  
    73.     ''' <summary>
    74.     ''' Executes a method after a pause.
    75.     ''' </summary>
    76.     ''' <param name="pause">
    77.     ''' The period of time after which to execute the method.
    78.     ''' </param>
    79.     ''' <param name="method">
    80.     ''' Refers to the method to execute.
    81.     ''' </param>
    82.     ''' <param name="arguments">
    83.     ''' The arguments to pass to the method when executing it.
    84.     ''' </param>
    85.     Public Sub ExecuteAfterPause(pause As Integer, method As [Delegate], ParamArray arguments As Object())
    86.         'Create a Timer to provide the delay that will raise its Elapsed event only once.
    87.         Dim timer As New Timers.Timer(pause) With {.AutoReset = False}
    88.  
    89.         'Package up the information about the method to execute.
    90.         'The context is required so that the method is invoked on the same thread as we are executing on now.
    91.         Dim info As New InvocationInfo With {.method = method,
    92.                                              .arguments = arguments,
    93.                                              .context = SynchronizationContext.Current}
    94.  
    95.         invocationInfoByTimer.Add(timer, info)
    96.         AddHandler timer.Elapsed, AddressOf Timer_Elapsed
    97.         timer.Start()
    98.     End Sub
    99.  
    100.     ''' <summary>
    101.     ''' Executes the method.
    102.     ''' </summary>
    103.     ''' <param name="info">
    104.     ''' Contains information about the method to invoke.
    105.     ''' </param>
    106.     Private Sub Execute(info As Object)
    107.         With DirectCast(info, InvocationInfo)
    108.             .method.DynamicInvoke(.arguments)
    109.         End With
    110.     End Sub
    111.  
    112. #End Region 'Methods
    113.  
    114. 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:
    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2.     DoSomething()
    3. End Sub
    4.  
    5. Private Sub DoSomething()
    6.     MessageBox.Show(Date.Now.ToString())
    7.     Thread.Sleep(5000)
    8.     MessageBox.Show(Date.Now.ToString())
    9. End Sub
    you can do this to create a pause that doesn't freeze anything:
    vb.net Code:
    1. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    2.     StartSomething()
    3. End Sub
    4.  
    5. Private Sub StartSomething()
    6.     MessageBox.Show(Date.Now.ToString())
    7.     ExecuteAfterPause(5000, New MethodInvoker(AddressOf FinishSomething))
    8. End Sub
    9.  
    10. Private Sub FinishSomething()
    11.     MessageBox.Show(Date.Now.ToString())
    12. 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:
    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2.     DoSomething(100, "John Smith")
    3. End Sub
    4.  
    5. Private Sub DoSomething(count As Integer, name As String)
    6.     MessageBox.Show(Date.Now.ToString())
    7.     Thread.Sleep(5000)
    8.     MessageBox.Show("Count: " & count, name)
    9. End Sub
    could become this:
    vb.net Code:
    1. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    2.     StartSomething(100, "John Smith")
    3. End Sub
    4.  
    5. Private Sub StartSomething(count As Integer, name As String)
    6.     MessageBox.Show(Date.Now.ToString())
    7.     ExecuteAfterPause(5000,
    8.                       New Action(Of Integer, String)(AddressOf FinishSomething),
    9.                       New Object() {count, name})
    10. End Sub
    11.  
    12. Private Sub FinishSomething(count As Integer, name As String)
    13.     MessageBox.Show("Count: " & count, name)
    14. End Sub
    or this:
    vb.net Code:
    1. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    2.     StartSomething(100, "John Smith")
    3. End Sub
    4.  
    5. Private Sub StartSomething(count As Integer, name As String)
    6.     MessageBox.Show(Date.Now.ToString())
    7.     ExecuteAfterPause(5000,
    8.                       New Action(Of Integer, String)(AddressOf FinishSomething),
    9.                       count,
    10.                       name)
    11. End Sub
    12.  
    13. Private Sub FinishSomething(count As Integer, name As String)
    14.     MessageBox.Show("Count: " & count, name)
    15. End Sub
    Attached Files Attached Files
    Last edited by jmcilhinney; Apr 14th, 2013 at 03:35 AM. Reason: Added module code file

  2. #2
    VB Addict Pradeep1210's Avatar
    Join Date
    Apr 2004
    Location
    Inside the CPU...
    Posts
    6,614

    Re: Adding a Pause to Your Code

    Great info

    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

    Read articles on My Blog 101 LINQ Samples JSON Validator XML Schema Validator "How Do I" videos on MSDN VB.NET and C# Comparison Good Coding Practices VBForums Reputation Saver String Enum Super Simple Tetris Game


    (2010-2013)
    NB: I do not answer coding questions via PM. If you want my help, then make a post and PM me it's link. If I can help, trust me I will...

  3. #3

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    104,961

    Re: Adding a Pause to Your Code

    Quote Originally Posted by Pradeep1210 View Post
    Great info

    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.

  4. #4
    PowerPoster i00's Avatar
    Join Date
    Mar 2002
    Location
    1/2 way accross the galaxy.. and then some
    Posts
    2,347

    Re: Adding a Pause to Your Code

    Quote Originally Posted by jmcilhinney View Post
    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:
    1. Dim t As New System.Threading.Thread(AddressOf TestLoop)
    2.  
    3.     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    4.         t.Suspend()
    5.     End Sub
    6.  
    7.     Private Sub TestLoop()
    8.  
    9.         Do
    10.             Static i As Integer
    11.             i += 1
    12.             Me.Invoke(Function() SetFormText(i.ToString))
    13.             System.Threading.Thread.Sleep(1000)
    14.         Loop
    15.  
    16.     End Sub
    17.  
    18.     Private Function SetFormText(ByVal str As String) As Boolean
    19.         Me.Text = str
    20.         Return True
    21.     End Function
    22.  
    23.     Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    24.         Button1.Text = "Pause"
    25.         Button2.Text = "Resume"
    26.         t.Start()
    27.     End Sub
    28.  
    29.     Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    30.         t.Resume()
    31.     End Sub

    Kris

  5. #5

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    104,961

    Re: Adding a Pause to Your Code

    Quote Originally Posted by i00 View Post
    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.

  6. #6
    PowerPoster i00's Avatar
    Join Date
    Mar 2002
    Location
    1/2 way accross the galaxy.. and then some
    Posts
    2,347

    Re: Adding a Pause to Your Code

    Quote Originally Posted by jmcilhinney View Post
    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:
    1. Dim TriggerPaused As Boolean
    2.     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    3.         TriggerPaused = True
    4.     End Sub
    5.  
    6.     Dim wh As New System.Threading.EventWaitHandle(False, Threading.EventResetMode.AutoReset)
    7.  
    8.     Private Sub TestLoop()
    9.  
    10.         Do
    11.             Static i As Integer
    12.             i += 1
    13.             Me.Invoke(Function() SetFormText(i.ToString))
    14.             System.Threading.Thread.Sleep(1000)
    15.  
    16.             If TriggerPaused = True Then
    17.                 TriggerPaused = False
    18.                 wh.WaitOne(Integer.MaxValue)
    19.             End If
    20.         Loop
    21.  
    22.     End Sub
    23.  
    24.     Private Function SetFormText(ByVal str As String) As Boolean
    25.         Me.Text = str
    26.         Return True
    27.     End Function
    28.  
    29.     Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    30.         Button1.Text = "Pause"
    31.         Button2.Text = "Resume"
    32.         Dim t As New System.Threading.Thread(AddressOf TestLoop) With {.IsBackground = True}
    33.         t.Start()
    34.     End Sub
    35.  
    36.     Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    37.         wh.Set()
    38.     End Sub

    Kris

  7. #7

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    104,961

    Re: Adding a Pause to Your Code

    Quote Originally Posted by i00 View Post
    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.

  8. #8
    VB Addict Pradeep1210's Avatar
    Join Date
    Apr 2004
    Location
    Inside the CPU...
    Posts
    6,614

    Re: Adding a Pause to Your Code

    Quote Originally Posted by jmcilhinney View Post
    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

    Read articles on My Blog 101 LINQ Samples JSON Validator XML Schema Validator "How Do I" videos on MSDN VB.NET and C# Comparison Good Coding Practices VBForums Reputation Saver String Enum Super Simple Tetris Game


    (2010-2013)
    NB: I do not answer coding questions via PM. If you want my help, then make a post and PM me it's link. If I can help, trust me I will...

  9. #9
    Super Moderator dday9's Avatar
    Join Date
    Mar 2011
    Location
    South Louisiana
    Posts
    9,588

    Re: Adding a Pause to Your Code

    Hi JMcIlhinney,

    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!

  10. #10

    Thread Starter
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    104,961

    Re: Adding a Pause to Your Code

    Quote Originally Posted by dday9 View Post
    Hi JMcIlhinney,

    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.

  11. #11
    Fanatic Member
    Join Date
    Dec 2009
    Posts
    546

    Re: Adding a Pause to Your Code

    Really amazing work John :0

  12. #12
    Frenzied Member
    Join Date
    Dec 2014
    Location
    VB6 dinosaur land
    Posts
    1,191

    Re: Adding a Pause to Your Code

    My question has been moved to a new thread.
    Last edited by topshot; Feb 6th, 2015 at 11:34 AM. Reason: Moved question to its own thread

  13. #13
    Bad man! ident's Avatar
    Join Date
    Mar 2009
    Location
    Cambridge
    Posts
    5,350

    Re: Adding a Pause to Your Code

    I have just seen this submission John. Very nice little work around over thread sleep in a loop which i see often.

  14. #14
    Hyperactive Member
    Join Date
    Jan 2009
    Posts
    429

    Re: Adding a Pause to Your Code

    This is amazing piece of info

    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?
    Never Give UP! It is Mindset that brings you success!

  15. #15
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,115

    Re: Adding a Pause to Your Code

    Quote Originally Posted by yogesh12 View Post
    This is amazing piece of info

    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

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width