1

I've written a custom Start-SleepNoUIHang function to sleep a windows form UI whilst not hanging it to allow for user interaction using a ForEach loop and inside that loop it calls [System.Windows.Forms.Application]::DoEvents() to prevent it from doing so.

It works as I intended but the only trouble is that the function slightly drifts past the argument $Milliseconds.

If I set that to say 5000 the timer takes around 6300 milliseconds.

I've tried to add a counter inside the ForEach loop and then break out of it once it reaches the $Milliseconds argument but that doesn't seem to work.

I didn't want to use the .net timer so I created this as a one-liner to use anywhere in the program where it was needed.

Any help here would be greatly appreciated.

Here's the code (with comments):


<#

This function attempts to pause the UI without hanging it without the need for a
timer event that does work.

The only trouble is that the timer slight drifts more than the provided
$Milliseconds argument.

#>

function Start-SleepNoUIHang {

  Param
  (
    [Parameter(Mandatory = $false, HelpMessage = 'The time to wait in milliseconds.')]
    [int]$Milliseconds
    )

  $timeBetween = 50 # This value seems to be a good value in order for the UI not to hang itself.
  $timeElapsed = 0 # Increment this to check and break out of the ForEach loop.

  # ($Milliseconds/$timeBetween)*$timeBetween # Time is the total wait time in milliseconds.

  1..($Milliseconds/$timeBetween) | ForEach {

    Start-Sleep -Milliseconds $timeBetween
    Try { [System.Windows.Forms.Application]::DoEvents() } catch{} # A try catch here in case there's no windows form.
    $timeElapsed = $timeElapsed + $timeBetween # Increment the $timeElapsed counter.

    Write-Host $timeElapsed

    # This doesn't seem to have any effect on the timer. It ends on its own accord.
    if ($timeElapsed -gt $Milliseconds) {
      Write-Host 'Break'
      break
    }

  }

}

$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
Write-Host "Started at $(get-date)"

Start-SleepNoUIHang -Milliseconds 5000

Write-Host "Ended at $(get-date)"
Write-Host "Total Elapsed Time: $($elapsed.Elapsed.ToString())"

I've also tried to do a While loop replacing the ForEach loop with this but that behaved the same.

  While ( $Milliseconds -gt $timeElapsed ) {

    $timeElapsed = $timeElapsed + $timeBetween # Increment the $timeElapsed counter.
    Start-Sleep -Milliseconds $timeBetween

    Write-Host $timeElapsed

  }
Ste
  • 1,729
  • 1
  • 17
  • 27
  • 1
    In a gui, use a [System.Windows.Forms.Timer](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.timer), not the `System.Diagnostics.Stopwatch` and do your stuff inside its `Tick` event. – Theo Jun 26 '21 at 11:32
  • The stopwatch at the bottom is only there to record the time it takes for the function. Perhaps the timer can be put inside a function like I've done here and only have one line to pause which was my main objective in writing this function. So far this works great, the only issue is it drifts off. (Not that bg of a deal but I would like to fix that if possible). – Ste Jun 26 '21 at 11:45
  • 1
    See: [Start-Sleep pauses entire function](https://stackoverflow.com/a/54046047/1701026) – iRon Jun 26 '21 at 11:52
  • You might also do some background reading on the: [Use of Application.DoEvents()](https://stackoverflow.com/a/5183623/1701026) – iRon Jun 26 '21 at 13:19
  • The invocation of `Start-Sleep` itself invariably takes time itself, so the call as a whole will invariably take longer than the specified interval, and there are no guarantees with respect to _how much_ longer. Also note that you should only use `break` inside an actual _loop_ (which a `ForEach-Object` call is _not_ - see [this answer](https://stackoverflow.com/a/67642758/45375)). – mklement0 Jun 27 '21 at 22:30
  • 1
    @mklement0. Oh, `return` should be used thanks. I ended up using the `While...` with `break` approach as opposed to `ForEach`. I also added an offset of +14 ms like so: `$timeElapsed = $timeElapsed + $timeBetween+14` which worked within a 100 ms, testing from 1 second to 20 seconds. I did some reading on `DoEvents` and as long as it's only updating a simple UI element it appears to work fine which is all I'm doing. My main multithreaded stuff is in a scriptblock with a timer updating the UI during that process. – Ste Jun 27 '21 at 23:08

0 Answers0