Is there an asynchronous equivalent to Process.Start?

Like in the header, is there an equivalent to Process.Start (allows you to run another application or batch file), what can I wait?

I play with a small console application, and it seemed like the perfect place to use async and wait, but I can not find the documentation for this scenario.

What I think is something like that:

 void async RunCommand() { var result = await Process.RunAsync("command to run"); } 
+123
c # async-await
May 28 '12 at 18:28
source share
5 answers

Process.Start() only starts the process, it does not wait for its completion, so it makes no sense to make it async . If you still want to do this, you can do something like await Task.Run(() => Process.Start(fileName)) .

But if you want to wait asynchronously for the process to complete, you can use the Exited event along with TaskCompletionSource :

 static Task<int> RunProcessAsync(string fileName) { var tcs = new TaskCompletionSource<int>(); var process = new Process { StartInfo = { FileName = fileName }, EnableRaisingEvents = true }; process.Exited += (sender, args) => { tcs.SetResult(process.ExitCode); process.Dispose(); }; process.Start(); return tcs.Task; } 
+170
May 28 '12 at 18:50
source share

Here is my answer based on svick answer . It adds output redirection, saving the exit code, and slightly better error handling (deleting the Process object, even if it cannot be started):

 public static async Task<int> RunProcessAsync(string fileName, string args) { using (var process = new Process { StartInfo = { FileName = fileName, Arguments = args, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true }, EnableRaisingEvents = true }) { return await RunProcessAsync(process).ConfigureAwait(false); } } private static Task<int> RunProcessAsync(Process process) { var tcs = new TaskCompletionSource<int>(); process.Exited += (s, ea) => tcs.SetResult(process.ExitCode); process.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data); process.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data); bool started = process.Start(); if (!started) { //you may allow for the process to be re-used (started = false) //but I'm not sure about the guarantees of the Exited event in such a case throw new InvalidOperationException("Could not start process: " + process); } process.BeginOutputReadLine(); process.BeginErrorReadLine(); return tcs.Task; } 
+52
Jul 18 '15 at
source share

Here's a different approach. The concept is similar to svick and Ohad answers , but using an extension method for the Process type.

Extension Method:

 public static Task RunAsync(this Process process) { var tcs = new TaskCompletionSource<object>(); process.EnableRaisingEvents = true; process.Exited += (s, e) => tcs.TrySetResult(null); // not sure on best way to handle false being returned if (!process.Start()) tcs.SetException(new Exception("Failed to start process.")); return tcs.Task; } 

An example of use in a containing method:

 public async Task ExecuteAsync(string executablePath) { using (var process = new Process()) { // configure process process.StartInfo.FileName = executablePath; process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; // run process asynchronously await process.RunAsync(); // do stuff with results Console.WriteLine($"Process finished running at {process.ExitTime} with exit code {process.ExitCode}"); };// dispose process } 
+4
May 29 '18 at 21:48
source share

I created a class to start the process, and it has grown in recent years due to different requirements. During use, I found several problems with the Process class with deleting and even reading ExitCode. It is processed by the class and is referred to as code comment.

The class has several capabilities, for example, reading output, running as an administrator or another user, catching exceptions, and also starting all this asynchronous on. Cancel It's nice that reading the results is also possible at runtime.

 public class ProcessSettings { public string FileName { get; set; } public string Arguments { get; set; } = ""; public string WorkingDirectory { get; set; } = ""; public string InputText { get; set; } = null; public int Timeout_milliseconds { get; set; } = -1; public bool ReadOutput { get; set; } public bool ShowWindow { get; set; } public bool KeepWindowOpen { get; set; } public bool StartAsAdministrator { get; set; } public string StartAsUsername { get; set; } public string StartAsUsername_Password { get; set; } public string StartAsUsername_Domain { get; set; } public bool DontReadExitCode { get; set; } public CancellationToken CancellationToken { get; set; } } public class ProcessOutputReader // Optional, to get the output while executing instead only as result at the end { public event TextEventHandler OutputChanged; public event TextEventHandler OutputErrorChanged; public void UpdateOutput(string text) { OutputChanged?.Invoke(this, new TextEventArgs(text)); } public void UpdateOutputError(string text) { OutputErrorChanged?.Invoke(this, new TextEventArgs(text)); } public delegate void TextEventHandler(object sender, TextEventArgs e); public class TextEventArgs : EventArgs { public string Text { get; } public TextEventArgs(string text) { Text = text; } } } public class ProcessResult { public string Output { get; set; } public string OutputError { get; set; } public int ExitCode { get; set; } public bool WasCancelled { get; set; } public bool WasSuccessful { get; set; } } public class ProcessStarter { public ProcessResult Execute(ProcessSettings settings, ProcessOutputReader outputReader = null) { return Task.Run(() => ExecuteAsync(settings, outputReader)).GetAwaiter().GetResult(); } public async Task<ProcessResult> ExecuteAsync(ProcessSettings settings, ProcessOutputReader outputReader = null) { if (settings.FileName == null) throw new ArgumentNullException(nameof(ProcessSettings.FileName)); if (settings.Arguments == null) throw new ArgumentNullException(nameof(ProcessSettings.Arguments)); var cmdSwitches = "/Q " + (settings.KeepWindowOpen ? "/K" : "/C"); var arguments = $"{cmdSwitches} {settings.FileName} {settings.Arguments}"; var startInfo = new ProcessStartInfo("cmd", arguments) { UseShellExecute = false, RedirectStandardOutput = settings.ReadOutput, RedirectStandardError = settings.ReadOutput, RedirectStandardInput = settings.InputText != null, CreateNoWindow = !(settings.ShowWindow || settings.KeepWindowOpen), }; if (!string.IsNullOrWhiteSpace(settings.StartAsUsername)) { if (string.IsNullOrWhiteSpace(settings.StartAsUsername_Password)) throw new ArgumentNullException(nameof(ProcessSettings.StartAsUsername_Password)); if (string.IsNullOrWhiteSpace(settings.StartAsUsername_Domain)) throw new ArgumentNullException(nameof(ProcessSettings.StartAsUsername_Domain)); if (string.IsNullOrWhiteSpace(settings.WorkingDirectory)) settings.WorkingDirectory = Path.GetPathRoot(Path.GetTempPath()); startInfo.UserName = settings.StartAsUsername; startInfo.PasswordInClearText = settings.StartAsUsername_Password; startInfo.Domain = settings.StartAsUsername_Domain; } var output = new StringBuilder(); var error = new StringBuilder(); if (!settings.ReadOutput) { output.AppendLine($"Enable {nameof(ProcessSettings.ReadOutput)} to get Output"); } if (settings.StartAsAdministrator) { startInfo.Verb = "runas"; startInfo.UseShellExecute = true; // Verb="runas" only possible with ShellExecute=true. startInfo.RedirectStandardOutput = startInfo.RedirectStandardError = startInfo.RedirectStandardInput = false; output.AppendLine("Output couldn't be read when started as Administrator"); } if (!string.IsNullOrWhiteSpace(settings.WorkingDirectory)) { startInfo.WorkingDirectory = settings.WorkingDirectory; } var result = new ProcessResult(); var taskCompletionSourceProcess = new TaskCompletionSource<bool>(); var process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; try { process.OutputDataReceived += (sender, e) => { if (e?.Data != null) { output.AppendLine(e.Data); outputReader?.UpdateOutput(e.Data); } }; process.ErrorDataReceived += (sender, e) => { if (e?.Data != null) { error.AppendLine(e.Data); outputReader?.UpdateOutputError(e.Data); } }; process.Exited += (sender, e) => { try { (sender as Process)?.WaitForExit(); } catch (InvalidOperationException) { } taskCompletionSourceProcess.TrySetResult(false); }; try { process.Start(); } catch (System.ComponentModel.Win32Exception ex) { if (ex.NativeErrorCode == 1223) { error.AppendLine("AdminRights request Cancelled by User!! " + ex); taskCompletionSourceProcess.SetException(ex); } else { error.AppendLine("Win32Exception thrown: " + ex); taskCompletionSourceProcess.SetException(ex); } } catch (Exception ex) { error.AppendLine("Exception thrown: " + ex); taskCompletionSourceProcess.SetException(ex); } if (success && startInfo.RedirectStandardOutput) process.BeginOutputReadLine(); if (success && startInfo.RedirectStandardError) process.BeginErrorReadLine(); if (success && startInfo.RedirectStandardInput) { var writeInputTask = Task.Factory.StartNew(() => WriteInputTask()); } async void WriteInputTask() { var processRunning = true; await Task.Delay(50).ConfigureAwait(false); try { processRunning = !process.HasExited; } catch { } while (processRunning) { if (settings.InputText != null) { try { await process.StandardInput.WriteLineAsync(settings.InputText).ConfigureAwait(false); await process.StandardInput.FlushAsync().ConfigureAwait(false); settings.InputText = null; } catch { } } await Task.Delay(5).ConfigureAwait(false); try { processRunning = !process.HasExited; } catch { processRunning = false; } } } if (success && settings.CancellationToken != default(CancellationToken)) settings.CancellationToken.Register(() => taskCompletionSourceProcess.TrySetResult(true)); if (success && settings.Timeout_milliseconds > 0) new CancellationTokenSource(settings.Timeout_milliseconds).Token.Register(() => taskCompletionSourceProcess.TrySetResult(true)); var taskProcess = taskCompletionSourceProcess.Task; await taskProcess.ConfigureAwait(false); if (taskProcess.Result == true) // process was cancelled by token or timeout { if (!process.HasExited) { result.WasCancelled = true; error.AppendLine("Process was cancelled!"); try { process.CloseMainWindow(); await Task.Delay(30).ConfigureAwait(false); if (!process.HasExited) { process.Kill(); } } catch { } } } result.ExitCode = settings.DontReadExitCode ? -1 : process.ExitCode; // Reason: sometimes, like when timeout /t 30 is started, reading the ExitCode is only possible if the timeout expired, even if process.Kill was called before. process.Close(); } finally { var disposeTask = Task.Factory.StartNew(() => process.Dispose()); } // start in new Task because disposing sometimes waits until the process is finished, for example while executing following command: ping -n 30 -w 1000 127.0.0.1 > nul if (result.ExitCode == -1073741510 && !result.WasCancelled) { error.AppendLine($"Process exited by user!"); } result.WasSuccessful = !result.WasCancelled && result.ExitCode == 0; result.Output = output.ToString(); result.OutputError = error.ToString(); return result; } } 
+2
May 11 '19 at 14:18
source share

I am really worried about the utilization of the process, how about waiting for the exit from the asynchronous? This is my suggestion (based on the previous one):

 public static class ProcessExtensions { public static Task WaitForExitAsync(this Process process) { var tcs = new TaskCompletionSource<object>(); process.EnableRaisingEvents = true; process.Exited += (s, e) => tcs.TrySetResult(null); return process.HasExited ? Task.CompletedTask : tcs.Task; } } 

Then use it like this:

 public static async Task<int> ExecAsync(string command, string args) { ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = command; psi.Arguments = args; using (Process proc = Process.Start(psi)) { await proc.WaitForExitAsync(); return proc.ExitCode; } } 
0
Feb 13 '19 at 2:51
source share



All Articles