2

.NET Core has the Process class that can start a process. You can also read its stdout and stderr streams and write something to stdin. And you can wait for the process to exit in a specified time. The problem is, it takes massive amounts of code and still doesn't work correctly in all situations.

Reading synchronously won't miss a line of output and tell you exactly when you have all output. But it may block if the called program generates too much output (> 4 kB?).

Reading asynchronously should resolve the "buffer full" blocking, but can't tell you when you have all the output. And it might miss some output at the start.

This question and all the comments of its highest voted answer summarise the diverse issues quite well.

So I'm looking for an implementation that can:

  • Start a process with arguments
  • Lets me send data to its input
  • Gets me the full contents of its output (stdout and stderr)
  • Waits for a specified time and kills the process if it didn't return timely
  • Lets me move on when I have the final result (timeout/exit code and all output so far)

The 2020 edition for .NET Core 3.1 on Windows and Linux.

It would also be nice to wait for the process asynchronously. I have some code for that, and again, it's huge for what it does.

If somebody from Microsoft could add the solution to their sparse documentation, it would be much appreciated. I couldn't find the relevant GitHub repo anymore to report that insufficient documentation.

Here's what I have now, and it doesn't work for larger output: (process won't exit and needs to be killed)

var psi = new ProcessStartInfo
{
    FileName = "dotnet",
    UseShellExecute = false,
    CreateNoWindow = true,
    RedirectStandardOutput = true,
    RedirectStandardError = true
};
psi.ArgumentList.Add("--list-runtimes");   // A few pages output on my PC
string standardOutput;
string standardError;
using (var process = Process.Start(psi))
{
    bool timedOut = false;
    // Sync version
    if (!process.WaitForExit(10000))
    {
        try
        {
            // Try to clean things up
            process.Kill();
        }
        catch
        {
            // Might have exited after all during the short period of time before
            // calling Kill(). And if it fails for other reasons, we can't help it here.
        }
        timedOut = true;
    }
    // BEGIN Async alternative
    using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
    try
    {
        await process.WaitForExitAsync(cts.Token);
    }
    catch (OperationCanceledException ex) when (ex.CancellationToken == cts.Token)
    {
        try
        {
            // Try to clean things up
            process.Kill();
        }
        catch
        {
            // Might have exited after all during the short period of time before
            // calling Kill(). And if it fails for other reasons, we can't help it here.
        }
        timedOut = true;
    }
    // END Async alternative

    standardOutput = process.StandardOutput.ReadToEnd();
    standardError = process.StandardError.ReadToEnd();

    if (timedOut)
    {
        logger?.LogError($"The command did not complete in time.\n" +
            $"Output: {standardOutput.TrimEnd()}\nError: {standardError.TrimEnd()}");
        standardOutput = null;
    }
    else if (process.ExitCode != 0)
    {
        logger?.LogError($"The command failed with exit code {process.ExitCode}.\n" +
            $"Output: {standardOutput.TrimEnd()}\nError: {standardError.TrimEnd()}");
        standardOutput = null;
    }
}
ygoe
  • 18,655
  • 23
  • 113
  • 210

0 Answers0