Wait-Event should allow more granularity
Summary of the new feature / enhancement
As a user of PowerShell events, I often find myself frustrated with the ways Wait-Event works.
Primarily this concerns the Timeout being in seconds.
That may seem like a little thing, but it has a major downside: when I'm expecting events to be quick, this makes me wait a whole second I may not need to wait.
Additionally, based off of the way the command is written, the shortest possible wait is 1/5th of a second.
Proposed technical implementation details (optional)
Wait-Event should have a -Timespan parameter that takes a particular Timespan.
Most of the internal logic should remain the same, with the exception of modifying the "floor" value, which is currently 200ms.
The floor value should instead be one tenth of the timeout.
This is something I'm happy to handle myself as a small community improvement.
I recommend full CancellationToken support rather than piecemeal timeout options
If you have CancellationToken support that gives you CancelAfter(TimeSpan) in addition to cancellation scenarios not involving time
My proof of concept demonstrates using CancellationToken with wait-event ( that is 100 milliseconds in the example below )
$cancellationTokenSource = New-Object -Type System.Threading.CancellationTokenSource
$cancellationToken = $cancellationTokenSource.Token
try
{
try
{
Invoke-CommandWithCancellationToken -ScriptBlock {
$cancellationTokenSource.CancelAfter(100)
Wait-Event
} -CancellationToken $cancellationToken -NoNewScope
}
catch
{
($PSItem.Exception.CancellationToken -eq $cancellationToken)
}
}
finally
{
$cancellationTokenSource.Dispose()
}
FYI, in adding the -Timespan parameter and dropping down the polling interval to 1/100th of the -Timespan, I was able to get a response back from an event in ~34ms, instead of ~200ms: 588% faster!
Why is Wait-Event dependent on anything to do with time or polling?
It should block and do absolutely nothing until an event has been queued and then unblock and process.
Polling, loops with timeouts all looks like a hack. Unfortunately that is what the code actually looks like... :(
while (!received)
{
if (_timeoutInSeconds >= 0)
{
if ((DateTime.UtcNow - startTime).TotalSeconds > _timeoutInSeconds)
break;
}
received = _eventArrived.WaitOne(200);
eventManager.ProcessPendingActions();
}
I hope that this code part means the WaitOne could be an infinite block as it sets the _eventArrived.
private void NotifyEvent(PSEventArgs e)
{
if (_receivedEvent == null)
{
lock (_receivedEventLock)
{
if (_receivedEvent == null)
{
_receivedEvent = e;
_eventArrived.Set();
}
}
}
}
@rhubarb-geek-nz yep, you've answered your own question.
I don't love the approach at play here either, but it was fairly straightforward to improve how it worked.
I'm not quite sure what a succinct integration of the CancellationToken would look like. If you want to provide a sample inline, I can see about updating the PR (your prior sample used some command that isn't in core).
See https://github.com/rhubarb-geek-nz/CancellationTokenEvent/blob/main/CancellationTokenEvent/CommandWithCancellationToken.cs
The relevant part is ...
using (CancellationTokenRegistration registration = CancellationToken.Register(() =>
{
powerShell.Stop();
}))
{
... run the long running operation. ...
}
I can see about updating the PR (your prior sample used some command that isn't in core).
No, but it is in the PSGallery
The loop should have calculated the timeout time before the loop and if there is no timeout it should use WaitOne, eg in pseudo code
if timeout then. calculate Expiry time
while (!received && !Stopping)
{
if cancellationToken!=null and cancellationToken.IsCancellationRequested break
if has timeout
if current time later than expiry time then break
_eventArrived.WaitOne(number of milliseconds remaining till timeout)
else
_eventArrived.WaitOne()
... etc ...
}
So if there is a timeout then the wait is for the exact number of milliseconds remaining, and if no timeout then it blocks completely. In the case of a cancellation token, the callback would call StopProcessing().
The WG discussed this and since we've been adding support for timespan in other locations, it's very reasonable to include this for Wait-Event.
The WG discussed this and since we've been adding support for timespan in other locations, it's very reasonable to include this for Wait-Event.
Good to hear. I've updated the PR for this (#19705) and the PSTransform attribute (#20299).
I'd very much like to see [TimeSpan] supported elsewhere as well. Please let me know if you have any other places that need it.