解析从更新单个控制台行的进程输出

5

问候stackoverflow会员们,

在WPF前端的BackgroundWorker中,我使用System.Diagnostics.Process运行sox(开源控制台声音处理工具)。我以同样的方式使用其他几个命令行工具,并解析它们的输出来填充我的前端中的进度栏。

这对于其他工具有效,但对于Sox而言却不是这样,因为它不会为每个进度步骤产生新行,而是只使用回车符(\r)而没有换行符(\n)在控制台上更新单行。我尝试了在process.StandardError上进行异步和同步读取。

使用process.ErrorDataReceived += (sender, args) => FadeAudioOutputHandler(clip, args);process.BeginErrorReadLine();组合起来不能产生任何单独的状态更新,因为由于某种原因,回车符不会触发ReadLine,即使MSDN文档表明它应该。当进程完成时,输出将一次性输出。

然后,我尝试了以下代码,在流上进行同步的char by char读取:

char[] c;
var line = new StringBuilder();
while (process.StandardError.Peek() > -1)
{
    c = new char[1];
    process.StandardError.Read(c, 0, c.Length);
    if (c[0] == '\r')
    {
        var percentage = 0;
        var regex = new Regex(@"%\s([^\s]+)");
        var match = regex.Match(line.ToString());
        if (match.Success)
        {
            myProgressObject.ProgressType = ProgressType.FadingAudio
            //... some calculations omitted for brevity
            percentage = (int) Math.Round(result);
        }
        else
        {
            myProgressObject.ProgressType = ProgressType.UndefinedStep;
        }
        _backGroundWorker.ReportProgress(percentage, myProgressObject);
        line.Clear();
    }
    else
    {
        line.Append(c[0]);
    }
}

上面的代码似乎不会实时读取流,但会在一段时间内停止输出。然后它会发送一小块内容,最后在过程中死锁。
如果有任何指向正确方向的提示将不胜感激!
(草率?)解决方案更新:
这让我疯狂,因为我在C#方面尝试的所有东西似乎都没有对结果产生任何影响。在更改15次并引入新的依赖项之前,我的原始实现是好的。
问题出在sox和RedirectStandardError上。我在抓取了sox源代码并构建了自己的版本后发现了这一点。首先我完全删除了sox的所有输出,除了我真正感兴趣的内容,然后将输出更改为完整行,后面跟一个换行符\n。我以为这会解决我的问题。好吧,它没有。我不知道足够的C++来找出为什么,但他们似乎已经干预了stdio将如何写入该流,缓冲它的方式或以这种特殊的方式去做,以至于C#端的streamreader直到默认的4096字节缓冲区被填满之前都不会刷新。我通过将每行填充到至少4096个字节来确认了这一点。所以最后的结论就是,在sox.c中的每个fprintf(stderr, …)调用之后手动刷新stderr即可:display_status(...)
fflush(stderr);

虽然我不确定这是否是一个优雅的解决方案。

感谢Erik Dietrich的回答,让我从不同角度看待这个问题。


你关于sox的信息让我很好奇,所以我进行了一些研究。这可能有点冒险,但是http://en.wikipedia.org/wiki/Setvbuf有一些有趣的可能性。看起来你可以通过文件句柄而不是进程来强制缓冲设置。这可能让你“覆盖”sox的默认行为,这样你就可以保留它的源代码而不必每次更新他们的东西时都注定要手动更新。可能会有一个托管的C#等效版本,或者您可以编写并调用一个小型的C实用程序。 - Erik Dietrich
3个回答

9

谢谢您的回答。我尝试使用您提供链接中的ProcessIoManager,但结果相同。输出在开始时挂起,然后以块状形式输出,在长时间任务中每个块之间有5秒的延迟。如果我将您链接中的示例GUI与sox结合使用,情况也是如此。似乎RedirectStandardError和sox输出存在问题。在命令行上运行sox不会显示这些症状。 - Till
5秒的延迟是streamreader缓冲区填充所需的时间,请查看我的更新问题(如果有兴趣)。 - Till
谢谢更新……根据您的描述,这取决于实现,因此我认为除了更改SOX以外,没有其他选择,因为这是导致此行为的实现方式,接收方无法改变。 - Yahia

2

虽然有点笨拙,但也许你可以将不合作的进程的输出导入到一个处理字符输入、插入换行符并写入标准输出的进程中... 因此,以下是(非常)伪代码:

StartProcess("sox | littleguythatIwrote")
ReadStandardOutTheWayYouAleadyAre()

可能这只是把目标移到了另一个地方(我在NIX世界更熟悉标准输入/输出/错误流),但这是解决问题的另一种方式。


感谢让我注意到sox这方面的问题。如果你有兴趣,可以查看我的更新问题,看看我想出了什么。 - Till
嗯...有趣。我想知道是否可以以某种方式代表另一个进程刷新标准错误(似乎不太可能,但谁知道呢?)。我能想到的唯一方法是以某种方式欺骗sox,让它认为它正在与用户交互。如果我没记错的话,C库对于STD I/O的行为会因为它们认为直接被用户调用而有所不同。但再说一遍,这个记忆是关于Linux上的C,所以要持怀疑态度。 - Erik Dietrich

2
我曾经在Visual Studio的自定义构建工具中遇到了类似的问题。我发现,在同一线程中使用正则表达式进行解析和读取会导致输出处理停滞不前。最终,我采用了标准的生产者消费者解决方案,即从输出中读取行并将它们放入队列中,然后在其他线程中对队列进行出队和处理。我无法提供源代码,但这个网站有一些很棒的资源:http://www.albahari.com/threading/part2.aspx

这似乎不是问题,即使注释掉状态更新的间隔检查,每分钟几千行的正则表达式部分也没有引起任何问题。 - Till

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接