使用RedirectStandardInput和RedirectStandardOutput调用jpegOptim

时间:2015-11-05 03:00:34

标签: c# process stream redirectstandardoutput jpegoptim

我正在尝试做一些看起来应该相对简单的事情:从C#调用jpegoptim。

我可以让它写入磁盘很好,但是让它接受一个流并发出一个流到目前为止还没有找到我 - 我总是以0长度输出或不祥的“管道已经结束。”

我试过的一种方法:

var processInfo = new ProcessInfo(
    jpegOptimPath,
    "-m" + quality + " -T1 -o -p --strip-all --all-normal"
);
processInfo.CreateNoWindow = true;
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardInput = true;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;

using(var process = Process.Start(processInfo))
{
    await Task.WhenAll(
        inputStream.CopyToAsync(process.StandardInput.BaseStream),
        process.StandardOutput.BaseStream.CopyToAsync(outputStream)
    );

    while (!process.HasExited)
    {
        await Task.Delay(100);
    }

    // Do stuff with outputStream here - always length 0 or exception
}

我也试过这个解决方案:

http://alabaxblog.info/2013/06/redirectstandardoutput-beginoutputreadline-pattern-broken/

using (var process = new Process())
{
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.CreateNoWindow = true;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.FileName = fileName;
    process.StartInfo.Arguments = arguments;

    process.Start();

    //Thread.Sleep(100);

    using (Task processWaiter = Task.Factory.StartNew(() => process.WaitForExit()))
    using (Task<string> outputReader = Task.Factory.StartNew(() => process.StandardOutput.ReadToEnd()))
    using (Task<string> errorReader = Task.Factory.StartNew(() => process.StandardError.ReadToEnd()))
    {
        Task.WaitAll(processWaiter, outputReader, errorReader);

        standardOutput = outputReader.Result;
        standardError = errorReader.Result;
    }
}

同样的问题。输出长度为0.如果我让jpegoptim在没有输出重定向的情况下运行,我会得到我所期望的 - 一个优化的文件 - 但是当我以这种方式运行它时不会。

有必要这样做吗?

更新:发现一条线索 - 我不觉得羞怯 - jpegoptim从未支持到stdin的管道直到2014年的实验性构建,今年已修复。我的版本来自2013年的旧图书馆。https://github.com/tjko/jpegoptim/issues/6

1 个答案:

答案 0 :(得分:0)

部分解决方案 - 请参阅下面的死锁问题。我原来的尝试中遇到了多个问题:

  1. 您需要构建jpegoptim,它将读取和写入管道而不是仅限文件。如上所述builds prior to mid-2014 can't do it。 jpegoptim的github“releases”是源代码的无用拉链,而不是内置的版本,因此您需要在其他地方查找实际的built releases

  2. 您需要正确调用它,传递--stdin--stdout,并根据您对它的响应方式,避免可能导致其无法写入的参数,例如 - T1(当优化仅为1%或更低时,将导致它不向stdout发射任何东西)。

  3. 您需要执行以下重要任务:在Process类上重定向输入和输出

  4. 并避免输入端的缓冲区溢出,再次使您获得0输出 - 明显的stream.CopyToAsync()溢出进程的非常有限的4096字节(4K)缓冲区并且什么也得不到。

  5. 这么多路线都没有。没有说明原因。

    var processInfo = new ProcessInfo(
        jpegOptimPath,
        "-m" + quality + " --strip-all --all-normal --stdin --stdout",
    );
    
    processInfo.CreateNoWindow = true;
    processInfo.WindowStyle = ProcessWindowStyle.Hidden;
    
    processInfo.UseShellExecute = false;
    processInfo.RedirectStandardInput = true;
    processInfo.RedirectStandardOutput = true;
    processInfo.RedirectStandardError = true;
    
    using(var process = new Process())
    {
        process.StartInfo = processInfo;
    
        process.Start();
    
        int chunkSize = 4096;   // Process has a limited 4096 byte buffer
        var buffer = new byte[chunkSize];
        int bufferLen = 0;
        var inputStream = process.StandardInput.BaseStream;
        var outputStream = process.StandardOutput.BaseStream;
    
        do
        {
            bufferLen = await input.ReadAsync(buffer, 0, chunkSize);
            await inputStream.WriteAsync(buffer, 0, bufferLen);
            inputStream.Flush();
        }
        while (bufferLen == chunkSize);
    
        do
        {
            bufferLen = await outputStream.ReadAsync(buffer, 0, chunkSize);
            if (bufferLen > 0)
                await output.WriteAsync(buffer, 0, bufferLen);
        }
        while (bufferLen > 0);
    
        while(!process.HasExited)
        {
            await Task.Delay(100);
        }
    
        output.Flush();
    

    这里有一些需要改进的地方。欢迎改进。

    • 最大问题:在某些图片上,outputStream.ReadAsync行发生此死锁。

    • 这一切都属于单独的方法来分解它 - 我展开了一堆方法来保持这个例子的简单。

    • 有许多可能没有必要的冲洗。

    • 此处的代码旨在处理流入和流出的任何内容。 4096是任何进程都会处理的硬限制,但假设所有输入进入,然后输出的所有输出都可能是坏的,并且基于我的研究可能导致其他类型进程的死锁。似乎jpegoptim在传递--stdin --stdout时表现为这种(非常缓冲,非常类似...)的方式,但是,这个代码很好地应对了这个特定的任务。