1

我正在尝试做一些看起来应该相对简单的事情:从 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 直到 2014 年的实验性版本才支持管道到标准输入,并于今年修复。我拥有的版本来自一个较旧的库,日期为 2013 年。https://github.com/tjko/jpegoptim/issues/6

4

1 回答 1

0

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

  1. 您需要一个 jpegoptim 构建,它将读取和写入管道而不是仅文件。如前所述,2014 年年中之前的构建无法做到这一点。jpegoptim的 github“版本”是无用的源代码压缩包,而不是构建版本,因此您需要在其他地方寻找实际构建版本

  2. 您需要正确调用它,传递--stdinand --stdout,并根据您将如何响应它,避免可能导致它什么都不写的参数,例如 -T1 (当优化将只有 1% 或更少时,使其不向标准输出发出任何内容)。

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

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

这么多的路线一无所获。没有说明为什么。

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 是任何进程都会处理的硬限制,但是假设所有输入都进入,然后所有输出都出来,这可能是一个糟糕的假设,并且根据我的研究,可能会导致其他类型的进程陷入僵局。但是,当通过 --stdin --stdout 时,jpegoptim 似乎以这种(非常缓冲,非常类似 unpipe...)的方式运行,因此,此代码可以很好地应对此特定任务。

于 2015-11-05T16:39:33.973 回答