Interrupting a running function

From AutoIt Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

This is a common question and this tutorial explains how to do it within the limitations of Autoit.

AutoIt queues function calls in both OnEvent and MessageLoop modes. This means that it waits until a function is finished and the code is back in your idle While...WEnd loop before running the next.

Here are 2 short scripts in both modes to show this in action. Start Function 1 by pressing its button and then immediately try to run Function 2 by pressing its button a couple of times. You will see from the console output that Function 2 will only run AFTER Function 1 has terminated - and then it will run as many times as you pressed the button.

We start with MessageLoop mode:


 #include <GUIConstantsEx.au3>
 
 $hGUI = GUICreate("Test", 500, 500)
 
 $hButton_1 = GUICtrlCreateButton("Func One", 10, 10, 80, 30)
 $hButton_2 = GUICtrlCreateButton("Func Two", 10, 50, 80, 30)
 
 GUISetState()
 
 While 1
     Switch GUIGetMsg()
         Case $GUI_EVENT_CLOSE
             Exit
         Case $hButton_1
             _Func_1()
         Case $hButton_2
         _Func_2()
     EndSwitch
 WEnd
 
 Func _Func_1()
     For $i = 1 To 20
         ConsoleWrite("-Func 1 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 1 Ended" & @CRLF)
 EndFunc
 
 Func _Func_2()
     For $i = 1 To 3
         ConsoleWrite("+Func 2 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 2 Ended" & @CRLF)
 EndFunc

And now in OnEvent mode:


 #include <GUIConstantsEx.au3>
 
 Opt("GUIOnEventMode", 1)
 
 $hGUI = GUICreate("Test", 500, 500)
 GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit") 
 
 $hButton_1 = GUICtrlCreateButton("Func One", 10, 10, 80, 30)
 GUICtrlSetOnEvent($hButton_1, "_Func_1")
 $hButton_2 = GUICtrlCreateButton("Func Two", 10, 50, 80, 30)
 GUICtrlSetOnEvent($hButton_2, "_Func_2")
 
 GUISetState()
 
 While 1
     Sleep(10)
 WEnd
 
 Func _Func_1()
 For $i = 1 To 20
     ConsoleWrite("-Func 1 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 1 Ended" & @CRLF)
 EndFunc
 
 Func _Func_2()
     For $i = 1 To 3
         ConsoleWrite("+Func 2 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 2 Ended" & @CRLF)
 EndFunc
 
 Func _Exit()
     Exit
 EndFunc

So is it impossible to interrupt a running function? Of course not or this tutorial would serve no purpose - but there are limitations on the type of function which can be interrupted.

If you are in OnEvent mode then there is an easy way to interrupt a running function, as long as the function is started within the main code and not by an OnEvent call. This script shows how to do it by using a flag and checking if it is set - in this way the function is started by the main script and can be interrupted.


 #include <GUIConstantsEx.au3>
 
 ; Declare a flag
 Global $fRunOne = False
 
 Opt("GUIOnEventMode", 1)
 
 $hGUI = GUICreate("Test", 500, 500)
 GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit")
 
 $hButton_1 = GUICtrlCreateButton("Func One", 10, 10, 80, 30)
 GUICtrlSetOnEvent($hButton_1, "_Func_1")
 $hButton_2 = GUICtrlCreateButton("Func Two", 10, 50, 80, 30)
 GUICtrlSetOnEvent($hButton_2, "_Func_2")
 
 GUISetState()
 
 While 1
     Sleep(10)
     ; Check if the flag has been set by the OnEvent function
     If $fRunOne Then
         ; Now start the "real" function from within the main code
         _Func_1_Run()
     EndIf
 WEnd
 
 Func _Func_1()
     ; Set the flag within the OnEvent function
     $fRunOne = True
 EndFunc   ;==>_Func_1
 
 Func _Func_1_Run()
     For $i = 1 To 20
         ConsoleWrite("-Func 1 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 1 Ended" & @CRLF)
     Global $fRunOne = False
 EndFunc   ;==>_Func_1_Run
 
 Func _Func_2()
     For $i = 1 To 3
         ConsoleWrite("+Func 2 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 2 Ended" & @CRLF)
 EndFunc   ;==>_Func_2
 
 Func _Exit()
     Exit
 EndFunc   ;==>_Exit

However, this method does not work in MessageLoop mode, nor when you want to interrupt a function which was started by an OnEvent call in OnEvent mode. In these cases the function you want to interrupt must have regular checks of a flag which we set when we want to interrupt. If the function does not have a suitable loop where such a check is easy to make, you might not be able to interrupt it.

Let us now look at the 2 ways an AutoIt script can break into a suitable function: a HotKey and a Windows message handler. The first is easier to code, but cannot be linked to a control on your GUI; the latter is a little more complicated to code, but can be linked to a control.

How do they work?

Let us start with the HotKey. From the AutoIt Help file: A hotkey-press *typically* interrupts the active AutoIt function/statement and runs its user function until it completes or is interrupted. So a HotKey press is designed to interrupt your running function - just what we need. But the running function will resume after the HotKey function terminates - so, as explained above, we use the HotKey function to set a flag which is looked for by our running function. Remember that this flag must be declared as a Global variable because it needs to be seen by both functions.

Now many coders do not like using HotKeys because they remain active while a script is running and can interfere with other apps. One way to get around this is to use Accelerator keys - these look very much like HotKeys to the user, but are only active in the script in which they are declared. Although Accelerator keys look like HotKeys, they are tied to GUI controls and you must use the message handler method explained below if you want to use them to interrupt a running function.

The Windows message handler method is a little more complicated and if you are not used to working with them, you might want to read the GUIRegisterMsg tutorial first.

What we do here is intercept the message that your GUI control sends when it is activated - the same message that AutoIt queues to run the required code once the running function terminates. We use our own message to get a sneak preview of this queuing and use it to set a similar flag to that described above. If our running function sees the flag, it terminates and AutoIt then runs the function waiting in the queue - simple really!

Here are scripts using both methods to show how HotKeys, Accelerator keys and GUI controls can interrupt a running function. Just as before, start Function_1 by pressing its button - but now you can interrupt it at will!

First in MessageLoop mode:


 #include <GUIConstantsEx.au3>
 #include <WindowsConstants.au3>
 
 ; Set a HotKey
 HotKeySet("x", "_Interrupt")
 
 ; Declare a flag
 $fInterrupt = 0
 
 $hGUI = GUICreate("Test", 500, 500)
 
 $hButton_1 = GUICtrlCreateButton("Func One", 10, 10, 80, 30)
 $hButton_2 = GUICtrlCreateButton("Func Two", 10, 50, 80, 30)
 
 ; Create a dummy control for the Accelerator to action when pressed
 $hAccelInterupt = GUICtrlCreateDummy()
 ; Set an Accelerator key to action the dummy control
 Dim $AccelKeys[1][2]=[ ["z", $hAccelInterupt] ]
 GUISetAccelerators($AccelKeys)
 
 GUISetState()
 
 ; Intercept Windows command messages with out own handler
 GUIRegisterMsg($WM_COMMAND, "_WM_COMMAND")
 
 While 1
     Switch GUIGetMsg()
         Case $GUI_EVENT_CLOSE
             Exit
         Case $hButton_1
             _Func_1()
         Case $hButton_2
             _Func_2()
     EndSwitch
 WEnd
 
 Func _Func_1()
     ; Make sure the flag is cleared
     $fInterrupt = 0
     For $i = 1 To 20
         ConsoleWrite("-Func 1 Running" & @CRLF)
         ; Look for the flag
         If $fInterrupt <> 0 Then
             ; The flag was set
             Switch $fInterrupt
                 Case 1
                     ConsoleWrite("!Func 1 interrrupted by Func 2" & @CRLF)
                 Case 2
                     ConsoleWrite("!Func 1 interrrupted by HotKey" & @CRLF)
                 Case 3
                     ConsoleWrite("!Func 1 interrrupted by Accelerator" & @CRLF)
             EndSwitch
             Return
         EndIf
         Sleep(100)
     Next
     ConsoleWrite(">Func 1 Ended" & @CRLF)
 EndFunc
 
 Func _Func_2()
     For $i = 1 To 3
         ConsoleWrite("+Func 2 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 2 Ended" & @CRLF)
 EndFunc
 
 Func _Interrupt()
     ; The HotKey was pressed so set the flag
     $fInterrupt = 2
 EndFunc
 
 Func _WM_COMMAND($hWnd, $Msg, $wParam, $lParam)
     ; The Func 2 button was pressed so set the flag
     If BitAND($wParam, 0x0000FFFF) =  $hButton_2 Then $fInterrupt = 1
     ; The dummy control was actioned by the Accelerator key so set the flag
     If BitAND($wParam, 0x0000FFFF) =  $hAccelInterupt Then $fInterrupt = 3
     Return $GUI_RUNDEFMSG
 EndFunc   ;==>_WM_COMMAND

And now in OnEvent mode:


 #include <GUIConstantsEx.au3>
 #include <WindowsConstants.au3>
 
 ; Set a HotKey
 HotKeySet("x", "_Interrupt_HotKey")
 
 ; Declare a flag
 $fInterrupt = 0
 
 Opt("GUIOnEventMode", 1)
 
 $hGUI = GUICreate("Test", 500, 500)
 GUISetOnEvent($GUI_EVENT_CLOSE, "_Exit")
 
 $hButton_1 = GUICtrlCreateButton("Func One", 10, 10, 80, 30)
 GUICtrlSetOnEvent($hButton_1, "_Func_1")
 $hButton_2 = GUICtrlCreateButton("Func Two", 10, 50, 80, 30)
 GUICtrlSetOnEvent($hButton_2, "_Func_2")
 
 ; Create a dummy control for the Accelerator to action
 $hAccelInterupt = GUICtrlCreateDummy()
 GUICtrlSetOnEvent($hAccelInterupt, "_Interrupt_Accel")
 ; Set an Accelerator key to action the dummy control
 Dim $AccelKeys[1][2]=[ ["z", $hAccelInterupt] ]
 GUISetAccelerators($AccelKeys)
 
 GUISetState()
 
 ; Intercept Windows command messages with out own handler
 GUIRegisterMsg($WM_COMMAND, "_WM_COMMAND")
 
 While 1
 	Sleep(10)
 WEnd
 
 Func _Func_1()
     ; Make sure the flag is cleared
     $fInterrupt = 0
     For $i = 1 To 20
         ConsoleWrite("-Func 1 Running" & @CRLF)
         ; Look for the flag
         If $fInterrupt <> 0 Then
             ; The flag was set
             Switch $fInterrupt
                 Case 1
                     ConsoleWrite("!Func 1 interrrupted by Func 2" & @CRLF)
                 Case 2
                     ConsoleWrite("!Func 1 interrrupted by HotKey" & @CRLF)
                 Case 3
                     ConsoleWrite("!Func 1 interrrupted by Accelerator" & @CRLF)
             EndSwitch
             Return
         EndIf
         Sleep(100)
     Next
     ConsoleWrite(">Func 1 Ended" & @CRLF)
 EndFunc
 
 Func _Func_2()
     For $i = 1 To 3
         ConsoleWrite("+Func 2 Running" & @CRLF)
         Sleep(100)
     Next
     ConsoleWrite(">Func 2 Ended" & @CRLF)
 EndFunc
 
 Func _Exit()
     Exit
 EndFunc
 
 Func _Interrupt_HotKey()
     ; The HotKey was pressed so set the flag
     $fInterrupt = 2
 EndFunc
 
 Func _Interrupt_Accel()
     ; This is an empty function for the dummy control linked to the Accelerator key
 EndFunc
 
 Func _WM_COMMAND($hWnd, $Msg, $wParam, $lParam)
     ; The Func 2 button was pressed so set the flag
     If BitAND($wParam, 0x0000FFFF) =  $hButton_2 Then $fInterrupt = 1
     ; The dummy control was actioned by the Accelerator key so set the flag
     If BitAND($wParam, 0x0000FFFF) =  $hAccelInterupt Then $fInterrupt = 3
     Return $GUI_RUNDEFMSG
 EndFunc   ;==>_WM_COMMAND

But what if you want to have a longish Sleep or some other function which waits for a fair time inside the running function? If you add a Sleep(5000) to Func_1 in the above examples, you will see that you have to wait until the function returns from the Sleep before the interrupt is actioned, which is not what we want:


 Func _Func_1()
 	; Make sure the flag is cleared
 	$fInterrupt = 0
 	For $i = 1 To 20
 		ConsoleWrite("-Func 1 Running" & @CRLF)
 		; Add a long Sleep ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 		Sleep(5000)
 		; Look for the flag
          	If $fInterrupt <> 0 Then
 			; The flag was set
 			Switch $fInterrupt
 				Case 1
 					ConsoleWrite("!Func 1 interrrupted by Func 2" & @CRLF)
 				Case 2
 					ConsoleWrite("!Func 1 interrrupted by HotKey" & @CRLF)
 				Case 3
 					ConsoleWrite("!Func 1 interrrupted by Accelerator" & @CRLF)
 			EndSwitch
 			Return
 		EndIf
 		Sleep(100)
 	Next
 	ConsoleWrite(">Func 1 Ended" & @CRLF)
 EndFunc   ;==>_Func_1

The answer is to put the blocking function inside a wrapper function which does check for the interrupt flag at regular intervals. Replace the Sleep line with a call to the wrapper function and add the wrapper function to the script:


 ; Use a wrapper function in place of the blocking function
 Func _Func_1()
 	; Make sure the flag is cleared
 	$fInterrupt = 0
 	For $i = 1 To 20
 		ConsoleWrite("-Func 1 Running" & @CRLF)
 		; Run a modified Sleep function which checks for the interrupt
 		If _Interrupt_Sleep(5000) Then ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 			; The flag was set
 			Switch $fInterrupt
 				Case 1
 					ConsoleWrite("!Func 1 interrrupted by Func 2" & @CRLF)
 				Case 2
 					ConsoleWrite("!Func 1 interrrupted by HotKey" & @CRLF)
 				Case 3
 					ConsoleWrite("!Func 1 interrrupted by Accelerator" & @CRLF)
 			EndSwitch
 			Return
 		EndIf
 		Sleep(100)
 	Next
 	ConsoleWrite(">Func 1 Ended" & @CRLF)
 EndFunc   ;==>_Func_1
 
 ; And here is the wrapper function itself
 Func _Interrupt_Sleep($iDelay)
 	; Get a timestamp
 	Local $iBegin = TimerInit()
 	; And run a tight loop
 	Do
 		; Use a minimum Sleep (could also be a WinWaitActive with a short timeout)
 		Sleep(10)
 		; Look for the interrrupt
 		If $fInterrupt Then
 			; And return True immediately if set
 			Return True
 		EndIf
 	Until TimerDiff($iBegin) > $iDelay
 	; Return False if timed out and no interrupt was set
 	Return False
 EndFunc   ;==>_Interrupt_Sleep

So there are the ways available in AutoIt to interrupt a running function. Remember that you must be in OnEvent mode and start the function from the main code - or have a function which checks internally whether it ought to allow an interruption. You may need to use a wrapper function if you want to use a blocking function such as Sleep or WinWaitActive. In all other cases you are are not going to be able to interrupt your function.