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;
}
}
@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