Skip to content

Instantly share code, notes, and snippets.

@georg-jung
Forked from AlexMAS/ProcessAsyncHelper.cs
Last active December 30, 2023 02:14
Show Gist options
  • Save georg-jung/3a8703946075d56423e418ea76212745 to your computer and use it in GitHub Desktop.
Save georg-jung/3a8703946075d56423e418ea76212745 to your computer and use it in GitHub Desktop.
Use https://github.com/Tyrrrz/CliWrap instead, the following code has some shortcomings
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
// based on https://gist.github.com/AlexMAS/276eed492bc989e13dcce7c78b9e179d
public static class ProcessAsyncHelper
{
public static async Task<ProcessResult> RunProcessAsync(string command, string arguments, int timeout)
{
var result = new ProcessResult();
using (var process = new Process())
{
process.StartInfo.FileName = command;
process.StartInfo.Arguments = arguments;
process.StartInfo.UseShellExecute = false;
//process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
var outputBuilder = new StringBuilder();
var outputCloseEvent = new TaskCompletionSource<bool>();
process.OutputDataReceived += (s, e) =>
{
if (e.Data == null)
{
outputCloseEvent.SetResult(true);
}
else
{
outputBuilder.Append(e.Data);
}
};
var errorBuilder = new StringBuilder();
var errorCloseEvent = new TaskCompletionSource<bool>();
process.ErrorDataReceived += (s, e) =>
{
if (e.Data == null)
{
errorCloseEvent.SetResult(true);
}
else
{
errorBuilder.Append(e.Data);
}
};
var isStarted = process.Start();
if (!isStarted)
{
result.ExitCode = process.ExitCode;
return result;
}
// Reads the output stream first and then waits because deadlocks are possible
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// Creates task to wait for process exit using timeout
var waitForExit = WaitForExitAsync(process, timeout);
// Create task to wait for process exit and closing all output streams
var processTask = Task.WhenAll(waitForExit, outputCloseEvent.Task, errorCloseEvent.Task);
// Waits process completion and then checks it was not completed by timeout
if (await Task.WhenAny(Task.Delay(timeout), processTask) == processTask && waitForExit.Result)
{
result.ExitCode = process.ExitCode;
result.Output = outputBuilder.ToString();
result.Error = errorBuilder.ToString();
}
else
{
try
{
// Kill hung process
process.Kill();
}
catch
{
// ignored
}
}
}
return result;
}
private static Task<bool> WaitForExitAsync(Process process, int timeout)
{
return Task.Run(() => process.WaitForExit(timeout));
}
public struct ProcessResult
{
public int? ExitCode;
public string Output;
public string Error;
}
}
@Tyrrrz
Copy link

Tyrrrz commented May 13, 2020

Thanks @georg-jung!

Just wanted to add a few comments since I've found your post:

no handling of ps.Start();

To the best of my knowledge, false can only be returned with shell execute, although I may be wrong. I have never had a false return though so in order to handle it I would need a test that exercises this behavior. Basically I'm waiting for the issue to happen first before solving it.

does many things that are imho out of scope for such a library

Interestingly enough, CliWrap also has all the features of ProcessX, which is the event stream execution model. So you don't have to compromise.

more complicated than ProcessStartAsync (maybe interesting if one plans to pipe binary data/wants to stream output of one process to another)

Same here, CliWrap has a powerful support for piping in which the configuration and execution are also separate.

@georg-jung
Copy link
Author

While they do share features, I liked yours better in terms of separation of concerns and think it's less opinionated, that's what I meant. Like deciding "developers tend to write things to the console instead of an ILogger" (by offering an API for one but not the other) is a thing I considered "out of scope" ;-); but after all that's just my opinion too, I guess. So - keep up the great work!

@Tyrrrz
Copy link

Tyrrrz commented May 13, 2020

❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment