来自进程输出的异步捕获无法正常工作

时间:2010-09-02 11:14:10

标签: c#

我正在开发一个网站控件,用户可以上传他的PDF,上传后第三方CLI工具启动以根据特定的配置文件验证PDF并生成相应的报告。 提到的工具是callas pdfToolbox 4(可在此处http://www.callassoftware.com/callas/doku.php/en:download[ ^])

问题在于我在网站上的控制权,我需要在检查PDF文件的实时进度条中显示。这个功能的所有AJAX东西都已经写好了(ajax-postbacks,进度条更新等),但是这个进程的异步更新存在问题,启动了pdf检查工具。

如果从命令行窗口启动该工具,您可以看到它生成标准输出流的输出,其中包含进度更新(百分比),以及有关PDF文件中错误的可能消息。 / p>

但是,如果该工具是由我在web控件中创建的进程启动的,那么在检查完成之前我不会收到OutputDataReceived事件,然后一次又一次地发出许多OutputDataReceived事件。

我的代码如下(我编写了一个小型控制台应用程序来更快地测试):

class Program
{

        static string appString = "path-to-callas-cli";
        static string argString = "path-to-pdf-and-path-to-report-and-path-to-callas-profile";

         static void Main(string[] args)
        {
             ProcessStartInfo pInfo = new ProcessStartInfo(appString, argString);
            pInfo.UseShellExecute = false;
            pInfo.CreateNoWindow = true;
            pInfo.RedirectStandardOutput = true;
            pInfo.RedirectStandardError = true;
            pInfo.RedirectStandardInput = true;
            pInfo.ErrorDialog = true;

            Process process = new Process();
            process.StartInfo = pInfo;
            process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
            process.Exited += new EventHandler(process_Exited);
            process.ErrorDataReceived += new DataReceivedEventHandler(process_ErrorDataReceived);
            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            process.WaitForExit();

            Console.ReadKey();
        }

        static void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {

        }

        static void process_Exited(object sender, EventArgs e)
        {
        }

        static void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            Console.WriteLine("Received async output: " + e.Data);
        }
}

正如我所说,Callas CLI的所有输出最终都会立即出现(检查大约需要35秒)。为了测试我自己的代码,我创建了一个小型控制台应用程序,以500毫秒的间隔输出数字10-20-30 -....- 100,它的输出完全显示在主应用程序中,随附间隔500毫秒。

有什么想法吗?

2 个答案:

答案 0 :(得分:1)

你需要在没有缓冲的情况下冲洗stdouts和printfs,以便在控制台上打印时接收它们......

请参阅C equivalent of autoflush (flush stdout after each write)?

我有一天试图解决这个问题,以为我会在同样的情况下为其他人分享解决方案......

答案 1 :(得分:0)

我遇到了完全相同的问题,从C#.NET调用CLI应用程序,我需要从中获得实时stdOut更新(归档程序的进度报告),但它没有发送退出流程之前的任何输出(压缩10分钟后无法获得进度报告

据我所知,这是进程的错误,而不是刷新stdOut缓冲区。我无法在 .Net 上找到任何方法来手动告诉进程刷新其stdOut。但是,我发现了一个hacky解决方案,能够实时读取stdOut。

我所做的就是访问工作Process.StandardOutput.BaseStream.ReadByte()方法,返回发送到stdOut的实际字节。这是纯粹的数据,包括马车操纵等。

然后将字节(似乎是ASCII?)转换为带有Char.ConvertFromUtf32(..)的字符,并将字符推入stringBuilder对象。现在,由于我有stdOut数据,我可以处理它,但我想要。

示例:对于我的情况,我想逐字捕获stdOut,然后回调给用户处理每个单词。

// - Standard process
proc = new Process();
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.filename = "...";
proc.start();

// - Init some things
int byte_r; // The byte that is going to be read from stdOut
StringBuilder word = new StringBuilder(); // Append the characters here
Action<String> onStdOutWord; // USERSET from before. Callbacks words read from stdOut

// As long as there is stdOut Data
while( (byte_r = proc.StandardOutput.BaseStream.ReadByte()) > -1 )
{   
    // If SPACE or ENTER callback the current word
    if(byte_r==32 || byte_r==13) {
        if(word.Length>0) {
            onStdOutWord(word.ToString());
            word.Clear();
        }
    }else{
        // Append character to string, skip special characters
        if(byte_r>32) {
            word.Append(Char.ConvertFromUtf32(byte_r));
        }
    }//-
}// - end while

然后,使用自定义回调onStdOutWord()我从CLI应用程序获取实时数据并处理它以获得我想要的内容。

上面的代码只是一个用例,因为我想获取文字,你可以改变它,让它像你想要的那样工作。 e.g。不分成单词并推出整个stringBuilder对象

我知道这是原始问题之后的8年,但我花了太多时间研究为什么我不能像OP一样获得标准数据,并且想要分享我的解决方案,以防其他人有同样的问题和绊倒在这个页面上,就像我做的那样