C#在读取async时等待shell命令返回

C#在读取async时等待shell命令返回,c#,asynchronous,C#,Asynchronous,我需要从C#应用程序运行一个外部进程,但要等待它返回,然后再继续执行。同时,我需要从stdout获取并更新一个包含进度信息的文本框。由于该命令可能会运行几分钟并在这段时间内打印信息,因此显示该命令是绝对必要的;但多个命令可能会运行,并且必须有序,因此等待也是如此 我尝试使用: p = Process.Start(); p.BeginOutputReadLine(); p.WaitForExit(); 但这会在等待时冻结UI线程,并阻止输出出现。这: p.Start(); p.BeginOutp

我需要从C#应用程序运行一个外部进程,但要等待它返回,然后再继续执行。同时,我需要从stdout获取并更新一个包含进度信息的文本框。由于该命令可能会运行几分钟并在这段时间内打印信息,因此显示该命令是绝对必要的;但多个命令可能会运行,并且必须有序,因此等待也是如此

我尝试使用:

p = Process.Start();
p.BeginOutputReadLine();
p.WaitForExit();
但这会在等待时冻结UI线程,并阻止输出出现。这:

p.Start();
p.BeginOutputReadLine();

while (!p.HasExited)
{
    Application.DoEvents();
    Thread.Sleep(100);
}
效果更好,但完全错误/一个坏主意,实际上没有等待整个周期

我还曾短暂尝试使用BackgroundWorker启动shell进程,但我不确定如何让UI线程在不阻塞worker的情况下等待它完成

我想做的是提供一个类似于
ShowDialog()
DialogResult ShellExecute(String cmd)
,如果用户允许命令完成、单击取消或命令的返回代码,则返回OK/Cancel(/fail)结果。在命令完成(或取消)之前,它不应返回

所有shell命令都使用以下命令运行:

ProcessStartInfo info = new ProcessStartInfo
{
    UseShellExecute = false,
    CreateNoWindow = true,
    RedirectStandardOutput = true,
    RedirectStandardError = true,

    FileName = "cmd.exe",
    Arguments = "/c " + command
};
要重定向输出并有效地执行shell命令


如何正确地创建一个启动异步进程的函数,但仍然要等到异步进程完成后才能返回?

从ProcessStartInfo创建进程

澄清:ProcessStartinfo si需要如下内容

si.CreateNoWindow=true;
si.RedirectStandardOutput=true;
si.RedirectStandardError=true;
si.StandardOutputEncoding=Encoding.UTF8;
si.StandardErrorEncoding=Encoding.UTF8;
si.WindowStyle=ProcessWindowStyle.Hidden;
然后以前

p.Start();
使用


修改了大部分代码以使其正常工作

ProgressForm
类提供了一个
QueueCommand
方法,获取所需的shell命令和pre/post委托。显示时,进度表单使用后台工作程序执行每个命令,处理返回代码,并在适当时执行下一个命令

后台工作程序等待每个shell命令完成(
Process.WaitForExit()
),同时异步提供UI线程输出。完成后,它调用一个方法,启用successful/ok按钮并隐藏进度条

void m_Worker_DoWork(object sender, DoWorkEventArgs e)
{
    int exec = 1;
    while (CommandQueue.Count > 0)
    {
        if (e.Cancel)
        {
            e.Result = 1;
            return;
        }

        WriteLine("Running command {0}, {1} remaining.", exec++, CommandQueue.Count);
        StagedCommand command = CommandQueue.Peek();
        try
        {
            if (command.Pre != null) command.Pre();
            int result = ShellExec(command.Command);
            if (command.Post != null) command.Post();
            CommandQueue.Dequeue();
            if (result != 0)
            {
                e.Result = result;
                return;
           }
        }
        catch (Exception exc)
        {
            WriteLine("Error: {0}", exc.Message);
            e.Result = 1;
            return;
        }
    }

    WriteLine("All commands executed successfully.");
    e.Result = 0;
    return;
}

    int ShellExec(String command)
    {
        WriteLine(command);
        Style = ProgressBarStyle.Marquee;

        ProcessStartInfo info = new ProcessStartInfo
        {
            UseShellExecute = false,
            LoadUserProfile = true,
            ErrorDialog = false,
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            RedirectStandardOutput = true,
            StandardOutputEncoding = Encoding.UTF8,
            RedirectStandardError = true,
            StandardErrorEncoding = Encoding.UTF8,

            FileName = "cmd.exe",
            Arguments = "/c " + command
        };

        Process shell = new Process();
        shell.StartInfo = info;
        shell.EnableRaisingEvents = true;
        shell.ErrorDataReceived += new DataReceivedEventHandler(ShellErrorDataReceived);
        shell.OutputDataReceived += new DataReceivedEventHandler(ShellOutputDataReceived);

        shell.Start();
        shell.BeginErrorReadLine();
        shell.BeginOutputReadLine();
        shell.WaitForExit();

        return shell.ExitCode;
    }

显然,如果要从StdErr捕获数据,则必须使用p.ErrorDataReceievd+=errorHandler重复此练习。这不会阻止函数创建和启动进程返回。我已经成功地重定向了输出,我更加关注无阻塞的等待。
private static void OutputHandler(object theProcess, DataReceivedEventArgs evtdata)
{
  //evtdata.Data has the output data
  //use it, display it, or discard it
}
void m_Worker_DoWork(object sender, DoWorkEventArgs e)
{
    int exec = 1;
    while (CommandQueue.Count > 0)
    {
        if (e.Cancel)
        {
            e.Result = 1;
            return;
        }

        WriteLine("Running command {0}, {1} remaining.", exec++, CommandQueue.Count);
        StagedCommand command = CommandQueue.Peek();
        try
        {
            if (command.Pre != null) command.Pre();
            int result = ShellExec(command.Command);
            if (command.Post != null) command.Post();
            CommandQueue.Dequeue();
            if (result != 0)
            {
                e.Result = result;
                return;
           }
        }
        catch (Exception exc)
        {
            WriteLine("Error: {0}", exc.Message);
            e.Result = 1;
            return;
        }
    }

    WriteLine("All commands executed successfully.");
    e.Result = 0;
    return;
}

    int ShellExec(String command)
    {
        WriteLine(command);
        Style = ProgressBarStyle.Marquee;

        ProcessStartInfo info = new ProcessStartInfo
        {
            UseShellExecute = false,
            LoadUserProfile = true,
            ErrorDialog = false,
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            RedirectStandardOutput = true,
            StandardOutputEncoding = Encoding.UTF8,
            RedirectStandardError = true,
            StandardErrorEncoding = Encoding.UTF8,

            FileName = "cmd.exe",
            Arguments = "/c " + command
        };

        Process shell = new Process();
        shell.StartInfo = info;
        shell.EnableRaisingEvents = true;
        shell.ErrorDataReceived += new DataReceivedEventHandler(ShellErrorDataReceived);
        shell.OutputDataReceived += new DataReceivedEventHandler(ShellOutputDataReceived);

        shell.Start();
        shell.BeginErrorReadLine();
        shell.BeginOutputReadLine();
        shell.WaitForExit();

        return shell.ExitCode;
    }