Last active
October 10, 2019 08:30
-
-
Save Legends/a9a42a30a2ec1dbb76bb3f989f5a18ed to your computer and use it in GitHub Desktop.
This file contains hidden or 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.Collections.Generic; | |
| using System.Diagnostics; | |
| using System.Threading; | |
| using System.Threading.Tasks; | |
| namespace RunProcessAsTask | |
| { | |
| public static partial class ProcessEx | |
| { | |
| public static async Task<ProcessResults> RunAsync(ProcessStartInfo processStartInfo, List<string> standardOutput, List<string> standardError, CancellationToken cancellationToken) | |
| { | |
| // force some settings in the start info so we can capture the output | |
| processStartInfo.UseShellExecute = false; | |
| processStartInfo.RedirectStandardOutput = true; | |
| processStartInfo.RedirectStandardError = true; | |
| var tcs = new TaskCompletionSource<ProcessResults>(); | |
| var process = new Process { | |
| StartInfo = processStartInfo, | |
| EnableRaisingEvents = true | |
| }; | |
| var standardOutputResults = new TaskCompletionSource<string[]>(); | |
| process.OutputDataReceived += (sender, args) => { | |
| if (args.Data != null) | |
| standardOutput.Add(args.Data); | |
| else | |
| standardOutputResults.SetResult(standardOutput.ToArray()); | |
| }; | |
| var standardErrorResults = new TaskCompletionSource<string[]>(); | |
| process.ErrorDataReceived += (sender, args) => { | |
| if (args.Data != null) | |
| standardError.Add(args.Data); | |
| else | |
| standardErrorResults.SetResult(standardError.ToArray()); | |
| }; | |
| var processStartTime = new TaskCompletionSource<DateTime>(); | |
| process.Exited += async (sender, args) => { | |
| // Since the Exited event can happen asynchronously to the output and error events, | |
| // we await the task results for stdout/stderr to ensure they both closed. We must await | |
| // the stdout/stderr tasks instead of just accessing the Result property due to behavior on MacOS. | |
| // For more details, see the PR at https://github.com/jamesmanning/RunProcessAsTask/pull/16/ | |
| tcs.TrySetResult( | |
| new ProcessResults( | |
| process, | |
| await processStartTime.Task.ConfigureAwait(false), | |
| await standardOutputResults.Task.ConfigureAwait(false), | |
| await standardErrorResults.Task.ConfigureAwait(false) | |
| ) | |
| ); | |
| }; | |
| using (cancellationToken.Register( | |
| () => { | |
| tcs.TrySetCanceled(); | |
| try { | |
| if (!process.HasExited) | |
| process.Kill(); | |
| } catch (InvalidOperationException) { } | |
| })) { | |
| cancellationToken.ThrowIfCancellationRequested(); | |
| var startTime = DateTime.Now; | |
| if (process.Start() == false) | |
| { | |
| tcs.TrySetException(new InvalidOperationException("Failed to start process")); | |
| } | |
| else | |
| { | |
| try | |
| { | |
| startTime = process.StartTime; | |
| } | |
| catch (Exception) | |
| { | |
| // best effort to try and get a more accurate start time, but if we fail to access StartTime | |
| // (for instance, process has already existed), we still have a valid value to use. | |
| } | |
| processStartTime.SetResult(startTime); | |
| process.BeginOutputReadLine(); | |
| process.BeginErrorReadLine(); | |
| } | |
| return await tcs.Task.ConfigureAwait(false); | |
| } | |
| } | |
| } | |
| } | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Diagnostics; | |
| using System.Linq; | |
| namespace RunProcessAsTask | |
| { | |
| public sealed class ProcessResults : IDisposable | |
| { | |
| public ProcessResults(Process process, DateTime processStartTime, string[] standardOutput, string[] standardError) | |
| { | |
| Process = process; | |
| ExitCode = process.ExitCode; | |
| RunTime = process.ExitTime - processStartTime; | |
| StandardOutput = standardOutput; | |
| StandardError = standardError; | |
| } | |
| public Process Process { get; } | |
| public int ExitCode { get; } | |
| public TimeSpan RunTime { get; } | |
| public string[] StandardOutput { get; } | |
| public string[] StandardError { get; } | |
| public void Dispose() { Process.Dispose(); } | |
| } | |
| } | |
| // --------------------------------------------- Usage Examples ----------------------------------------- | |
| // Synchronous, just easier way of grabbing output / error / runtime for the process | |
| Task<ProcessResults> processResults = ProcessEx.RunAsync("git.exe", "pull").Result; | |
| Console.WriteLine("Exit code: " + processResults.ExitCode); | |
| Console.WriteLine("Run time: " + processResults.RunTime); | |
| Console.WriteLine("{0} lines of standard output", processResults.StandardOutput.Length); | |
| foreach (var output in processResults.StandardOutput) | |
| { | |
| Console.WriteLine("Output line: " + output); | |
| } | |
| Console.WriteLine("{0} lines of standard error", processResults.StandardError.Length); | |
| foreach (var error in processResults.StandardError) | |
| { | |
| Console.WriteLine("Error line: " + error); | |
| } | |
| // Provide timeout for running process | |
| public async Task RunCommandWithTimeout(string filename, string arguments, TimeSpan timeout) | |
| { | |
| var processStartInfo = new ProcessStartInfo | |
| { | |
| FileName = filename, | |
| Arguments = arguments, | |
| }; | |
| try | |
| { | |
| using (var cancellationTokenSource = new CancellationTokenSource(timeout)) | |
| { | |
| var processResults = await ProcessEx.RunAsync(processStartInfo, cancellationTokenSource.Token); | |
| } | |
| } | |
| catch (OperationCanceledException) | |
| { | |
| Console.WriteLine("Timeout of {0} hit while trying to run {1} {2}", timeout, filename, arguments); | |
| } | |
| } | |
| Run multiple commands with dependencies in an async fashion | |
| public async Task ShowLastMatchingCommit(string regex) | |
| { | |
| var logProcessResults = await ProcessEx.RunAsync("git.exe", "log --pretty=oneline --all -n 1 -G" + regex); | |
| if (logProcessResults.ExitCode != 0) return; | |
| var stdoutSplit = logProcessResults.StandardOutput[0].Split(new[] { ' ' }, 2); | |
| var commitHash = stdoutSplit[0]; | |
| var commitMessage = stdoutSplit[1]; | |
| Console.WriteLine("Last commit matching {0} was {1} and had commit message {2}", regex, commitHash, commitMessage); | |
| var showProcessResults = await ProcessEx.RunAsync("git.exe", "show --pretty=fuller " + commitHash); | |
| foreach (var stdoutLine in showProcessResults.StandardOutput) | |
| { | |
| Console.WriteLine("git show output: " + stdoutLine); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment