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; | |
} | |
} |
This comment has been minimized.
This comment has been minimized.
A quick question, if we return from this line when process doesn't start, how would we know what is the issue in starting the process? |
This comment has been minimized.
This comment has been minimized.
Thanks for this! I added a quick and dirty change that allows for setting environment variables. https://github.com/sloppycombo/UIElements-Pug/blob/master/ProcessAsyncHelper.cs |
This comment has been minimized.
This comment has been minimized.
How do I implement this? The output and error data is being read async, but am I still just getting the data all at once when the process completes? |
This comment has been minimized.
This comment has been minimized.
It isn't quite easy to answer when |
This comment has been minimized.
This comment has been minimized.
Yes, you get the data when the process completes. This solution is not about streaming stdout/stderr asynchronously but about running the process in an async/await kind of way. If you need to call some external software which just offers a commandline interface, this can be quite useful. In the case I needed this solution for I did not need any streaming or parts of the stdout output. Consuming stdout as an async stream might be possible but is beyond this gist's scope. |
This comment has been minimized.
This comment has been minimized.
How can i use it, i dont understand when the Task is over? |
This comment has been minimized.
This comment has been minimized.
For a full blown example with some context see https://github.com/georg-jung/PdfAnnotator/blob/master/PdfAnnotator/Pdf/Poppler/Analyzer.cs#L32 Minimal working example: This will start your.exe with the given arguments and a timeout of 5 seconds. The task will run as long as the process it starts is running (mind the timeout), which means the |
This comment has been minimized.
This comment has been minimized.
Ok and how About admin priv? if i tries this with a exe that Need admin Rights...i got a Rights error :( |
This comment has been minimized.
This comment has been minimized.
Please see https://stackoverflow.com/a/133500/1200847 for how to elevate a process using the |
This comment has been minimized.
This comment has been minimized.
I made a generic one which can accept any I also took some improvements from the various answer from https://stackoverflow.com/questions/470256/process-waitforexit-asynchronously |
This comment has been minimized.
This comment has been minimized.
Once I was experimenting with something similar, focusing on streams in/out/error... Unfortunately the original link provided in the sample is not avail, but there were some consideration (at least in the past) why the event based approach was not used. What I miss is actually the ExitCode handling in case of regular completition (this includes everything except the custom timeout, so errors as well). This is what I could grab from the mails I've exchanged at that time: https://gist.github.com/hidegh/07d5588702e2b56a3fc2a3d73848d9f3 |
This comment has been minimized.
This comment has been minimized.
One goals of async is to not tie up a thread while waiting. This WaitForExitAsync shouldn't tie up a thread while waiting for process to complete.
|
This comment has been minimized.
This comment has been minimized.
I think you are right about that. The above code has different shortcomings. After reviewing (I read at least parts of the source code) some libraries solving this or similar problems, I think CliWrap is a quite complete and mature solution. More lightweight (in the sense of you can read the source code completely in some minutes, similar to this gist; not in the sense of execution time, which I didn't test) would be ProcessStartAsync, but it has some shortcomings:
I liked both more than other options I reviewed: https://github.com/Cysharp/ProcessX
https://github.com/itn3000/AsyncProcessExecutor/
https://github.com/jamesmanning/RunProcessAsTask
https://github.com/samoatesgames/AsyncProcess.Sharp
|
This comment has been minimized.
This comment has been minimized.
Thanks @georg-jung! Just wanted to add a few comments since I've found your post:
To the best of my knowledge,
Interestingly enough, CliWrap also has all the features of
Same here, CliWrap has a powerful support for piping in which the configuration and execution are also separate. |
This comment has been minimized.
This comment has been minimized.
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! |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This is definitely improvement over original Gist. Thank you for that.
As a precaution, you may want to add
CancellationTokenSource
in Line #71 for timeout operation.