如何刷新 ErrorDataReceived 以更快地处理?
How to refresh ErrorDataReceived to process faster?
我正在使用 sox.exe 播放一些音频文件。
我是这样称呼它的:
SoxPlayer = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
RedirectStandardError = true,
UseShellExecute = false,
FileName = Play,
Arguments = arg,
WorkingDirectory = Application.StartupPath + "\bin\"
}
};
这是解释 StandardError 输出的代码:
private void UpdatePlaybackTime(string output)
{
if (string.IsNullOrEmpty(output)) return;
if (!output.Contains("%") || !output.Contains("[")) return;
var index1 = output.IndexOf("%", StringComparison.Ordinal) + 1;
var index2 = output.IndexOf("[", StringComparison.Ordinal);
var time = output.Substring(index1, index2 - index1).Trim();
var times = time.Split(new[] { ":" }, StringSplitOptions.None);
var seconds = Convert.ToDouble(times[0]) * 3600;
seconds = seconds + (Convert.ToDouble(times[1]) * 60);
seconds = seconds + (Convert.ToDouble(times[2]));
if (seconds == 0 || seconds < PlaybackSeconds) return;
PlaybackSeconds = seconds;
}
我的目标是尽可能准确地从 sox 输出中获取播放时间,而不是使用可能与 sox 自身失去同步的内部计时器(就像我之前所做的那样)。
我的第一次尝试是使用我在网上找到的推荐:
SoxPlayer.ErrorDataReceived += (sender, args) => UpdatePlaybackTime(args.Data);
SoxPlayer.Start();
SoxPlayer.BeginErrorReadLine();
当前代码 "works" 中我得到了我想要的信息,但似乎每 5 秒左右调用一次 UpdatePlaybackTime()。调用时,获取的信息是准确的,但显然我想每秒更新几次播放信息,而不是每 5 秒一次。
我的理解是,当 StandardError 缓冲区变满时,正在调用 UpdatePlaybackTime。我试过用我的播放器计时器调用 SoxPlayer.BeginErrorReadLine() 但它说它已经在异步运行。我试过 SoxPlayer.StandardError.DiscardBufferedData() 但由于正在进行的异步进程,它会抛出异常。
那么,我怎样才能捕捉到我需要的播放信息呢?提前致谢!
编辑:
在讨论了这段代码以及它如何因缓冲而无法工作之后,我还在单独的 BackgroundWorker 线程中尝试了以下操作,结果相同(即大约每 5 秒更新一次):
SoxPlayer.Start();
SoxTimer.RunWorkerAsync();
private void SoxTimer_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
var sr = new StreamReader(SoxPlayer.StandardError.BaseStream);
while (sr.Peek() > 0)
{
var line = sr.ReadLine();
UpdatePlaybackTime(line);
}
}
private void SoxTimer_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (!SoxPlayer.HasExited)
{
SoxTimer.RunWorkerAsync();
}
}
当此 BackgroundWorker 完成时,它会检查是否 SoxPlayer.HasExited,如果没有,它会再次运行。这与我的第一次尝试具有相同的效果。 PlaybackSeconds 大约每 5 秒更新一次,此时它会更新到正确的时间,然后基于 PlaybackSeconds 值的其余代码也可以正常工作。
我也尝试通过创建线程来读取 StandardError 输出来实现相同的目的。每个实例都会产生相同的结果,在调用 UpdatePlaybackTime() 之间会有 5 秒左右的延迟。当它这样做时,它会遍历自我们上次遍历它以来发送到 StandardError 的所有输出,因此它会以小的增量非常快速地更新 PlaybackSeconds 值,并将其保留在当时的当前值。但同样,就用户而言,每 5 秒更新一次。
Sox 的创造者坚持认为问题不在他们这一端。在控制台窗口中播放时,输出是恒定的。根据 sox 创作者的说法,每 0.1 秒一次。如果我告诉 sox 将 is standarderror 输出到文本文件,同样的情况也会发生。文本文件上的信息不断更新。然而阅读 StandardError 流本身,我现在花了两天的大部分时间都没有可接受的结果。
感谢您的帮助。
编辑 2:
按照彼得在下面的建议,这是一个全新的项目。甚至没有更改任何内容的默认名称。与目前描述的行为相同。所以我要回去责备(咳咳,讨论)SoX 窥视者。
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Process SoxPlayer;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var bin = Application.StartupPath + "\bin\";
SoxPlayer = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
RedirectStandardError = true,
UseShellExecute = false,
FileName = bin + "sox.exe",
Arguments = "song.ogg -c 2 -d",
WorkingDirectory = bin
}
};
SoxPlayer.Start();
var thread = new Thread(() =>
{
int cch;
var rgch = new char[1];
while ((cch = SoxPlayer.StandardError.Read(rgch, 0, rgch.Length)) > 0)
{
var cch1 = cch;
label1.Invoke(new MethodInvoker(() => label1.Text = new string(rgch, 0, cch1)));
}
});
thread.Start();
}
private void button2_Click(object sender, EventArgs e)
{
SoxPlayer.Kill();
}
}
}
这是一个简单的代码示例,不会重现您描述的行为:
class Program
{
static void Main(string[] args)
{
Process process = new Process();
process.StartInfo.FileName = "SimpleStderrWriter.exe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.Start();
Thread thread = new Thread(() =>
{
int cch;
char[] rgch = new char[1];
Console.WriteLine("Reading stderr from process");
while ((cch = process.StandardError.Read(rgch, 0, rgch.Length)) > 0)
{
Console.Write(new string(rgch, 0, cch));
}
Console.WriteLine();
Console.WriteLine("Process exited");
});
Console.WriteLine("Press Enter to terminate process");
thread.Start();
Console.ReadLine();
process.StandardInput.WriteLine();
thread.Join();
}
}
这是 SimpleStderrWriter.exe
程序的代码:
class Program
{
static void Main(string[] args)
{
bool exit = false;
Thread thread = new Thread(() =>
{
while (!Volatile.Read(ref exit))
{
Console.Error.Write('.');
Thread.Sleep(250);
}
});
thread.Start();
Console.ReadLine();
Volatile.Write(ref exit, true);
thread.Join();
}
}
此代码示例通过在生成子进程的 stderr 输出时尽快接收和重新发出它,清楚地表明 .NET 中没有任何默认情况下会导致您遇到的延迟。显而易见的结论是,要么你的 SoX 顾问是错误的,它出于某种原因做了一些缓冲,要么你自己在代码中添加了一些东西,导致你遇到延迟。
如果您确定后者不是这种情况,那么您需要回到您的 SoX 顾问那里并向他们解释他们错了。如果您确定 SoX Advisor 是正确的,那么您需要 post 一个与上述类似的示例,但 确实 重现了您遇到的延迟。
我正在使用 sox.exe 播放一些音频文件。 我是这样称呼它的:
SoxPlayer = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
RedirectStandardError = true,
UseShellExecute = false,
FileName = Play,
Arguments = arg,
WorkingDirectory = Application.StartupPath + "\bin\"
}
};
这是解释 StandardError 输出的代码:
private void UpdatePlaybackTime(string output)
{
if (string.IsNullOrEmpty(output)) return;
if (!output.Contains("%") || !output.Contains("[")) return;
var index1 = output.IndexOf("%", StringComparison.Ordinal) + 1;
var index2 = output.IndexOf("[", StringComparison.Ordinal);
var time = output.Substring(index1, index2 - index1).Trim();
var times = time.Split(new[] { ":" }, StringSplitOptions.None);
var seconds = Convert.ToDouble(times[0]) * 3600;
seconds = seconds + (Convert.ToDouble(times[1]) * 60);
seconds = seconds + (Convert.ToDouble(times[2]));
if (seconds == 0 || seconds < PlaybackSeconds) return;
PlaybackSeconds = seconds;
}
我的目标是尽可能准确地从 sox 输出中获取播放时间,而不是使用可能与 sox 自身失去同步的内部计时器(就像我之前所做的那样)。
我的第一次尝试是使用我在网上找到的推荐:
SoxPlayer.ErrorDataReceived += (sender, args) => UpdatePlaybackTime(args.Data);
SoxPlayer.Start();
SoxPlayer.BeginErrorReadLine();
当前代码 "works" 中我得到了我想要的信息,但似乎每 5 秒左右调用一次 UpdatePlaybackTime()。调用时,获取的信息是准确的,但显然我想每秒更新几次播放信息,而不是每 5 秒一次。
我的理解是,当 StandardError 缓冲区变满时,正在调用 UpdatePlaybackTime。我试过用我的播放器计时器调用 SoxPlayer.BeginErrorReadLine() 但它说它已经在异步运行。我试过 SoxPlayer.StandardError.DiscardBufferedData() 但由于正在进行的异步进程,它会抛出异常。
那么,我怎样才能捕捉到我需要的播放信息呢?提前致谢!
编辑:
在讨论了这段代码以及它如何因缓冲而无法工作之后,我还在单独的 BackgroundWorker 线程中尝试了以下操作,结果相同(即大约每 5 秒更新一次):
SoxPlayer.Start();
SoxTimer.RunWorkerAsync();
private void SoxTimer_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
var sr = new StreamReader(SoxPlayer.StandardError.BaseStream);
while (sr.Peek() > 0)
{
var line = sr.ReadLine();
UpdatePlaybackTime(line);
}
}
private void SoxTimer_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (!SoxPlayer.HasExited)
{
SoxTimer.RunWorkerAsync();
}
}
当此 BackgroundWorker 完成时,它会检查是否 SoxPlayer.HasExited,如果没有,它会再次运行。这与我的第一次尝试具有相同的效果。 PlaybackSeconds 大约每 5 秒更新一次,此时它会更新到正确的时间,然后基于 PlaybackSeconds 值的其余代码也可以正常工作。
我也尝试通过创建线程来读取 StandardError 输出来实现相同的目的。每个实例都会产生相同的结果,在调用 UpdatePlaybackTime() 之间会有 5 秒左右的延迟。当它这样做时,它会遍历自我们上次遍历它以来发送到 StandardError 的所有输出,因此它会以小的增量非常快速地更新 PlaybackSeconds 值,并将其保留在当时的当前值。但同样,就用户而言,每 5 秒更新一次。
Sox 的创造者坚持认为问题不在他们这一端。在控制台窗口中播放时,输出是恒定的。根据 sox 创作者的说法,每 0.1 秒一次。如果我告诉 sox 将 is standarderror 输出到文本文件,同样的情况也会发生。文本文件上的信息不断更新。然而阅读 StandardError 流本身,我现在花了两天的大部分时间都没有可接受的结果。
感谢您的帮助。
编辑 2:
按照彼得在下面的建议,这是一个全新的项目。甚至没有更改任何内容的默认名称。与目前描述的行为相同。所以我要回去责备(咳咳,讨论)SoX 窥视者。
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Process SoxPlayer;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var bin = Application.StartupPath + "\bin\";
SoxPlayer = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
RedirectStandardError = true,
UseShellExecute = false,
FileName = bin + "sox.exe",
Arguments = "song.ogg -c 2 -d",
WorkingDirectory = bin
}
};
SoxPlayer.Start();
var thread = new Thread(() =>
{
int cch;
var rgch = new char[1];
while ((cch = SoxPlayer.StandardError.Read(rgch, 0, rgch.Length)) > 0)
{
var cch1 = cch;
label1.Invoke(new MethodInvoker(() => label1.Text = new string(rgch, 0, cch1)));
}
});
thread.Start();
}
private void button2_Click(object sender, EventArgs e)
{
SoxPlayer.Kill();
}
}
}
这是一个简单的代码示例,不会重现您描述的行为:
class Program
{
static void Main(string[] args)
{
Process process = new Process();
process.StartInfo.FileName = "SimpleStderrWriter.exe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.Start();
Thread thread = new Thread(() =>
{
int cch;
char[] rgch = new char[1];
Console.WriteLine("Reading stderr from process");
while ((cch = process.StandardError.Read(rgch, 0, rgch.Length)) > 0)
{
Console.Write(new string(rgch, 0, cch));
}
Console.WriteLine();
Console.WriteLine("Process exited");
});
Console.WriteLine("Press Enter to terminate process");
thread.Start();
Console.ReadLine();
process.StandardInput.WriteLine();
thread.Join();
}
}
这是 SimpleStderrWriter.exe
程序的代码:
class Program
{
static void Main(string[] args)
{
bool exit = false;
Thread thread = new Thread(() =>
{
while (!Volatile.Read(ref exit))
{
Console.Error.Write('.');
Thread.Sleep(250);
}
});
thread.Start();
Console.ReadLine();
Volatile.Write(ref exit, true);
thread.Join();
}
}
此代码示例通过在生成子进程的 stderr 输出时尽快接收和重新发出它,清楚地表明 .NET 中没有任何默认情况下会导致您遇到的延迟。显而易见的结论是,要么你的 SoX 顾问是错误的,它出于某种原因做了一些缓冲,要么你自己在代码中添加了一些东西,导致你遇到延迟。
如果您确定后者不是这种情况,那么您需要回到您的 SoX 顾问那里并向他们解释他们错了。如果您确定 SoX Advisor 是正确的,那么您需要 post 一个与上述类似的示例,但 确实 重现了您遇到的延迟。