Skip to content

Instantly share code, notes, and snippets.

@erdomke
Created July 30, 2021 16:51
Show Gist options
  • Save erdomke/7384fe15f485d5b8be974813c27907bb to your computer and use it in GitHub Desktop.
Save erdomke/7384fe15f485d5b8be974813c27907bb to your computer and use it in GitHub Desktop.
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace Namespace
{
internal class AsyncProcess
{
private Process _process = new Process();
private Channel<string> _stdError = Channel.CreateUnbounded<string>();
private ChannelWriter<string> _stdInput;
private Channel<string> _stdOut = Channel.CreateUnbounded<string>();
private TaskCompletionSource<int> _completion;
public int ExitCode => _process.ExitCode;
public bool HasExited => _process.HasExited;
public int Id => _process.Id;
public ProcessStartInfo StartInfo
{
get => _process.StartInfo;
set => _process.StartInfo = value;
}
public ChannelWriter<string> StandardInput => _stdInput;
public ChannelReader<string> StandardOutput => _stdOut.Reader;
public ChannelReader<string> StandardError => _stdError.Reader;
public void Kill(bool entireProcessTree = false)
{
if (_completion == null)
throw new InvalidOperationException("Cannot kill a process which hasn't started");
try
{
_completion.SetCanceled();
_process.Kill(entireProcessTree);
}
finally
{
_stdError.Writer.Complete();
_stdOut.Writer.Complete();
_process.Dispose();
}
}
public void Start()
{
_completion = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
_process.EnableRaisingEvents = true;
if (_process.StartInfo.RedirectStandardError
|| _process.StartInfo.RedirectStandardInput
|| _process.StartInfo.RedirectStandardOutput)
_process.StartInfo.UseShellExecute = false;
if (_process.StartInfo.RedirectStandardOutput)
{
_process.OutputDataReceived += new DataReceivedEventHandler(async (sender, e) =>
{
if (e.Data != null)
await _stdOut.Writer.WriteAsync(e.Data);
});
}
if (_process.StartInfo.RedirectStandardError)
{
_process.ErrorDataReceived += new DataReceivedEventHandler(async (sender, e) =>
{
if (e.Data != null)
await _stdError.Writer.WriteAsync(e.Data);
});
}
_process.Exited += new EventHandler((sender, e) =>
{
_stdError.Writer.Complete();
_stdOut.Writer.Complete();
_completion.SetResult(_process.ExitCode);
_process.Dispose();
});
if (_process.Start())
{
if (_process.StartInfo.RedirectStandardInput)
_stdInput = new StreamWriterChannel(_process.StandardInput);
if (_process.StartInfo.RedirectStandardOutput)
_process.BeginOutputReadLine();
if (_process.StartInfo.RedirectStandardError)
_process.BeginErrorReadLine();
}
else
{
var exception = new InvalidOperationException("A new process was not created");
exception.Data["FileName"] = _process.StartInfo.FileName;
exception.Data["Arguments"] = _process.StartInfo.Arguments;
throw exception;
}
}
public Task<int> WaitForExitAsync(CancellationToken cancellationToken = default)
{
if (_completion == null)
Start();
if (cancellationToken != default)
{
var registration = default(CancellationTokenRegistration);
registration = cancellationToken.Register(() =>
{
try
{
Kill();
}
finally
{
registration.Dispose();
}
});
_process.Exited += new EventHandler((sender, e) =>
{
registration.Dispose();
});
}
return _completion.Task;
}
public override bool Equals(object obj)
{
if (obj is AsyncProcess process)
return _process.Equals(process._process);
return false;
}
public override int GetHashCode()
{
return _process.GetHashCode();
}
public override string ToString()
{
return _process.ToString();
}
public static AsyncProcess Start(string fileName)
{
return Start(new ProcessStartInfo(fileName));
}
public static AsyncProcess Start(string fileName, string arguments)
{
return Start(new ProcessStartInfo(fileName, arguments));
}
public static AsyncProcess Start(ProcessStartInfo startInfo)
{
var result = new AsyncProcess() { StartInfo = startInfo };
result.Start();
return result;
}
private class StreamWriterChannel : ChannelWriter<string>
{
private readonly StreamWriter _writer;
public StreamWriterChannel(StreamWriter writer)
{
_writer = writer;
}
public override bool TryWrite(string item)
{
_writer.WriteLine(item);
return true;
}
public override ValueTask<bool> WaitToWriteAsync(CancellationToken cancellationToken = default)
{
return new ValueTask<bool>(true);
}
public override bool TryComplete(Exception error = null)
{
_writer.Close();
return true;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment