Powershell: Ctrl+C can't abort long running cmdlet

Created on 11 Mar 2020  路  11Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

(foo.vhd is a 36+GB file)

Get-FileHash foo.vhd
{hit Ctrl+C while running}

Expected behavior

The cmdlet should terminate "promptly" (probably with some exception)

Actual behavior

The cmdlet continues execution for "a long time"

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.0.0
PSEdition                      Core
GitCommitId                    7.0.0
OS                             Microsoft Windows 10.0.17763
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0鈥
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Area-Cmdlets-Utility Issue-Enhancement Waiting - DotNetCore

Most helpful comment

@mklement0

@iSazonov, do we already have cases of using .NET APIs that _do_ support cancellation that we _do_ surface via Ctrl-C?

Yeah, that's essentially what PSCmdlet.StopProcessing is for. When a pipeline stop is requested, that method is called in a different thread. If the cmdlet implements that method, it can then use whatever API specific logic is required to cancel (sometimes a CancellationToken, sometimes a method has a specific Stop operation like Ping.SendAsyncCancel()).

All 11 comments

We use .Net Core API which does not allow cancellation. You could use *-Job cmdlets as workaround.

Is your comment generic or only about Get-FileHash?

@sba923, I think what @iSazonov is saying is that Ctrl-C is ignored while a _.NET method_ is executing (as opposed to PowerShell code); since most methods execute quickly, this is usually not noticeable, but in your case it is.

You can verify the behavior as follows:

# Define a .NET class with a long-running method (effectively sleeps for 10 seconds).
Add-Type 'public class Foo { public void Bar() { for (var i = 0; i < 100; ++i) { System.Console.Write("."); System.Threading.Thread.Sleep(100); } } }'

# Ctrl-C has no effect while the .Bar() method is being executed.
[Foo]::new().Bar()

Is your comment generic or only about Get-FileHash?

My comment is about Get-FileHash.

I get the picture. But @iSazonov wrote "API _which does not_ allow cancellation." Is this a generic statement about _all_ .NET Core APIs, or (my understanding of the phrasing) about _that particular API_?

Shouldn't PowerShell wrap the calls to "potentially long-running .NET Core APIs" so that they can be aborted, whenever supported?

Good question, @sba923 - certainly, PowerShell supporting Ctrl-C consistently would make for a better user experience, but I can't speak to the technical feasibility.

@iSazonov, do we already have cases of using .NET APIs that _do_ support cancellation that we _do_ surface via Ctrl-C?

We could manually evaluate a hash by chunks and check cancellation on every iteration.
Or better run the work in another thread by Task.Run().

@iSazonov evaluating in chunks is a great idea 鉂わ笍

I'm less of a fan of just throwing it in another thread, since that gives the impression that the operation was cancelled. Also with Task.Run in particular, the potential for thread starvation in environments with low thread pool capacity is not ideal.

@mklement0

@iSazonov, do we already have cases of using .NET APIs that _do_ support cancellation that we _do_ surface via Ctrl-C?

Yeah, that's essentially what PSCmdlet.StopProcessing is for. When a pipeline stop is requested, that method is called in a different thread. If the cmdlet implements that method, it can then use whatever API specific logic is required to cancel (sometimes a CancellationToken, sometimes a method has a specific Stop operation like Ping.SendAsyncCancel()).

@SeeminglyScience don't remind me 馃槄

I'm still not sure that method _actually_ does a proper cancellation, it seems to take the same amount of time whether you cancel it or not. 馃榿

But yeah, janky Ping APIs aside, a lot of .NET async APIs have some kind of usable cancellation mechanism.

We will get ComputeHashAsync(Stream inputStream, CancellationToken cancellationToken = default)) in .Net Core 5.0.

Was this page helpful?
0 / 5 - 0 ratings