Results 1 to 15 of 15

Thread: Adding a Pause to Your Code

Threaded View

  1. #1

    Thread Starter
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    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

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