Is there any async equivalent of Process.Start?

Like the title suggests, is there an equivalent to Process.Start (allows you run another application or batch file) that I can await?

I'm playing with a small console app and this seemed like the perfect place to be using async and await but I can't find any documentation for this scenario.

What I'm thinking is something along these lines:

void async RunCommand()
{
    var result = await Process.RunAsync("command to run");
}

Process.Start() only starts the process, it doesn't wait until it finishes, so it doesn't make much sense to make it async. If you still want to do it, you can do something like await Task.Run(() => Process.Start(fileName)).

But, if you want to asynchronously wait for the process to finish, you can use the Exited event together 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;
}

The right way to run external process in .NET (async version) · GitHub, Like the title suggests, is there an equivalent to Process.Start (allows you run another application or batch file) that I can await?I'm playing with  Process.Start() only starts the process, it doesn’t wait until it finishes, so it doesn’t make much sense to make it async. If you still want to do it, you can do something like await Task.Run(() => Process.Start(fileName)).

Here's my take, based on svick's answer. It adds output redirection, exit code retention, and slightly better error handling (disposing the Process object even if it could not 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;
}

c#: Is there any async equivalent of Process.Start?, Ideally, you wouldn't use Task.Delay, and instead find a way to bind to Process event(s), and thereby create a brand new TaskCompletionSource. Also, you  188 Is there any async equivalent of Process.Start? View more network posts → Keeping a low profile. This user hasn't posted yet. Badges (1) Gold

Here's another approach. Similar concept to svick and Ohad's answers but using an extension method on 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;
}

Example use case 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
}

Process asynchronous tasks as they complete, The parent process starts a fresh python interpreter process. Changed in version 3.8: On macOS, the spawn start method is now the default. evaluate "f(​20)" asynchronously res = pool.apply_async(f, (20,)) # runs in *only* one process​  svick. Member for 11 years, 5 months. 189 Is there any async equivalent of Process.Start? View more network posts → Top tags (20) discussion. Score 36

I have built a class to start a process and it was growing over the last years because of various requirements. During the usage I found out several issues with the Process class with disposing and even reading the ExitCode. So this is all fixed by my class.

The class has several possibilities, for example reading output, start as Admin or different user, catch Exceptions and also start all this asynchronous incl. Cancellation. Nice is that reading output is also possible during execution.

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 bool ThrowExceptions { 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);
            };

            var success = false;
            try
            {
                process.Start();
                success = true;
            }
            catch (System.ComponentModel.Win32Exception ex)
            {
                if (ex.NativeErrorCode == 1223)
                {
                    error.AppendLine("AdminRights request Cancelled by User!! " + ex);
                    if (settings.ThrowExceptions) taskCompletionSourceProcess.SetException(ex); else taskCompletionSourceProcess.TrySetResult(false);
                }
                else
                {
                    error.AppendLine("Win32Exception thrown: " + ex);
                    if (settings.ThrowExceptions) taskCompletionSourceProcess.SetException(ex); else taskCompletionSourceProcess.TrySetResult(false);
                }
            }
            catch (Exception ex)
            {
                error.AppendLine("Exception thrown: " + ex);
                if (settings.ThrowExceptions) taskCompletionSourceProcess.SetException(ex); else taskCompletionSourceProcess.TrySetResult(false);
            }
            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 = -1;
            if (!settings.DontReadExitCode)     // 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.
            {
                try { result.ExitCode = process.ExitCode; }
                catch { output.AppendLine("Reading ExitCode failed."); }
            }
            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;
    }
}

Asynchronous programming in C#, process.stdout.fd; A note on process I/O traceDeprecation; process.umask(); process.umask(mask); process.uptime(); process.version; process.versions; Exit Codes work scheduled, but a listener registered on the 'beforeExit' event can make asynchronous calls, 'SIGUSR1' is reserved by Node.js to start the debugger. Usually there’s always a million library and samples floating around the web for any given task. However there is only client libraries in PHP, Python, and Java. Even searching for JWT examples outside of Google’s authentication, there is only crickets and drafts on the JWT concept.

Im really worried about disposal of process, what about wait for exit async?, this is my proposal (based on previous):

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;
    }
}

Starting a process asynchronous, If an application component starts and there already exists a process for AsyncTask allows you to perform asynchronous work on your user  Starts a process resource and associates it with a component. This overload lets you start a process without first creating a new Process instance. The overload is an alternative to the explicit steps of creating a new Process instance, setting the FileName, Arguments, UserName, Password, and Domain properties of the StartInfo property, and calling Start for the Process instance.

multiprocessing — Process-based parallelism, A new version of Appian is available! When an asynchronous sub-process is started, the parent process flow does not wait for the child process to complete. What you can do with Process.Start is really only limited by the processes you can use with it. If you want to display your text-based ReadMe file in Notepad, it's as easy as: If you want to display your text-based ReadMe file in Notepad, it's as easy as:

Process, .Net wrapper for the excellent ExifTool. Contribute to AerisG222/NExifTool development by creating an account on GitHub.

Processes and threads overview, The Start-Process cmdlet starts one or more processes on the local computer. To specify the program that runs in the process, enter an executable file or script file, or a file that can be opened by using a program on the computer. If you specify a non-executable file, Start-Process starts the program that is associated with the file, similar to the Invoke-Item cmdlet. You can use the

Comments
  • Why won't you just use WaitForExit on the returned Process object?
  • And by the way, sounds more like you're looking for a "synced" solution, rather than an "async" solution, so the title is misleading.
  • @YoryeNathan - lol. Indeed, Process.Start is async and the OP appears to want a synchronous version.
  • The OP is talking about the new async/await keywords in C# 5
  • Ok, I've updated my post to be a bit more clear. The explanation for why I want this is simple. Picture a scenario where you have to run an external command (something like 7zip) and then continue the flow of the application. This is exactly what async/await was meant to facilitate and yet there seems to be no way to run a process and await it's exit.
  • I finally got around to sticking something up on github for this - it doesn't have any cancellation/timeout support, but it'll gather the standard output and standard error for you, at least. github.com/jamesmanning/RunProcessAsTask
  • This functionality is also available in the MedallionShell NuGet package
  • Really important: The order in which you set the various properties on process and process.StartInfo changes what happens when you run it with .Start(). If you for example call .EnableRaisingEvents = true before setting StartInfo properties as seen here, things work as expected. If you set it later, for example to keep it together with .Exited, even though you call it before .Start(), it fails to work properly - .Exited fires immediately rather than waiting for the Process to actually exit. Do not know why, just a word of caution.
  • @svick In window form, process.SynchronizingObject should be set to forms component to avoid methods that handle events (such as Exited, OutputDataReceived, ErrorDataReceived) are called on separated thread.
  • It does actually make sense to wrap Process.Start in Task.Run. A UNC path, for example, will be resolved synchronously. This snippet can take up to 30 sec to complete: Process.Start(@"\\live.sysinternals.com\whatever")
  • just found this interesting solution. As I am new to c# I'm not sure how to use the async Task<int> RunProcessAsync(string fileName, string args). I adapted this example and pass three objects one by one. How can I await raising events? eg. before my application stopps.. thanks a lot
  • @marrrschine I don't understand exactly what you mean, perhaps you should start a new question with some code so we can see what you tried and continue from there.
  • Fantastic answer. Thank you svick for laying the groundwork and thank you Ohad for this very useful expansion.
  • @SuperJMN reading the code (referencesource.microsoft.com/#System/services/monitoring/…) I don't believe Dispose nulls the event handler, so theoretically if you called Dispose but kept the reference around, I believe that would be a leak. However, when there are no more references to the Process object and it gets (garbage) collected, there is no one that points to the event handler list. So it gets collected, and now there are no references to the delegates that used to be in the list, so finally they get garbage collected.
  • @SuperJMN: Interestingly, it's more complicated/powerful than that. For one, Dispose cleans up some resources, but doesn't prevent a leaked reference from keeping process around. In fact, you'll notice that process refers to the handlers, but the Exited handler also has a reference to process. In some systems, that circular reference would prevent garbage collection, but the algorithm used in .NET would still allow it to all be cleaned up so long as everything lives on an "island" with no outside references.