diff --git a/src/RunProcessAsTask/ProcessEx.cs b/src/RunProcessAsTask/ProcessEx.cs
index bcd90de..664331b 100644
--- a/src/RunProcessAsTask/ProcessEx.cs
+++ b/src/RunProcessAsTask/ProcessEx.cs
@@ -8,6 +8,8 @@ namespace RunProcessAsTask
{
public static partial class ProcessEx
{
+ private static readonly TimeSpan _processExitGraceTime = TimeSpan.FromSeconds(30);
+
///
/// Runs asynchronous process.
///
@@ -30,24 +32,33 @@ public static async Task RunAsync(ProcessStartInfo processStartI
};
var standardOutputResults = new TaskCompletionSource();
- process.OutputDataReceived += (sender, args) => {
+
+ void OutputDataReceived(object sender, DataReceivedEventArgs args)
+ {
if (args.Data != null)
standardOutput.Add(args.Data);
else
standardOutputResults.SetResult(standardOutput.ToArray());
- };
+ }
+
+ process.OutputDataReceived += OutputDataReceived;
var standardErrorResults = new TaskCompletionSource();
- process.ErrorDataReceived += (sender, args) => {
+
+ void ErrorDataReceived(object sender, DataReceivedEventArgs args)
+ {
if (args.Data != null)
standardError.Add(args.Data);
else
standardErrorResults.SetResult(standardError.ToArray());
- };
+ }
+
+ process.ErrorDataReceived += ErrorDataReceived;
var processStartTime = new TaskCompletionSource();
- process.Exited += async (sender, args) => {
+ async void OnExited(object sender, EventArgs args)
+ {
// Since the Exited event can happen asynchronously to the output and error events,
// we await the task results for stdout/stderr to ensure they both closed. We must await
// the stdout/stderr tasks instead of just accessing the Result property due to behavior on MacOS.
@@ -60,15 +71,38 @@ await standardOutputResults.Task.ConfigureAwait(false),
await standardErrorResults.Task.ConfigureAwait(false)
)
);
- };
+ }
+
+ process.Exited += OnExited;
using (cancellationToken.Register(
- () => {
- tcs.TrySetCanceled();
- try {
+ async () => {
+ try
+ {
if (!process.HasExited)
+ {
+ process.OutputDataReceived -= OutputDataReceived;
+ process.ErrorDataReceived -= ErrorDataReceived;
+ process.Exited -= OnExited;
process.Kill();
- } catch (InvalidOperationException) { }
+ await Task.Delay(TimeSpan.FromSeconds(1));
+ if (!process.HasExited)
+ {
+ if (!process.WaitForExit(_processExitGraceTime.Milliseconds))
+ {
+ throw new TimeoutException($"Timed out after {_processExitGraceTime.TotalSeconds:N2} seconds waiting for cancelled process to exit: {process}");
+ }
+ }
+ }
+ tcs.TrySetCanceled();
+ }
+ catch (InvalidOperationException)
+ {
+ }
+ catch (Exception exception)
+ {
+ tcs.SetException(new Exception($"Failed to kill process '{process.StartInfo.FileName}' ({process.Id}) upon cancellation", exception));
+ }
})) {
cancellationToken.ThrowIfCancellationRequested();