Created
July 30, 2021 16:51
-
-
Save erdomke/7384fe15f485d5b8be974813c27907bb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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