System.Diagnostics.Process实例可以被垃圾收集吗?
本文关键字:Process Diagnostics 实例 System | 更新日期: 2023-09-27 18:03:12
我使用System.Diagnostics.Process类在一个单独的进程中将wav文件转换为mp3文件。执行如下工作的方法:
public void ConvertWavToMp3 (TempFile srcFile, string title, Action<TempFile, Exception> complete)
{
var argument_fmt = "-S --resample 16 --tt {0} --add-id3v2 {1} {2}";
var dstFile = new TempFile(Path.GetTempFileName());
var proc = new System.Diagnostics.Process ();
proc.EnableRaisingEvents = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = "lame";
proc.StartInfo.Arguments = String.Format (argument_fmt,
title,
srcFile.Path,
dstFile.Path);
proc.Exited += delegate(object sender, EventArgs e) {
proc.WaitForExit();
srcFile.Delete();
complete(dstFile, null);
};
proc.Start();
}
我担心GC,因为proc只是一个局部变量,理论上当方法返回时它不再存在。因此,proc可以被垃圾收集,回调函数complete将永远不会被调用。
但是我真的不想在某个地方记录proc并在进程退出后处理它,因为这会暴露如何实现wav到mp3转换的内部机制。
我对GC的关注是否有效?如果GC是一个潜在的问题,有没有什么方法可以防止它,而不必在这个方法中返回进程?
顺便说一句,我在linux上使用Mono。
编辑
谢谢你的回复。我确认我需要保留一份流程的副本。我是这样做的:
public class LameConverter : IAudioConverter
{
// We need to store a reference to the process in case it was GCed.
IList<Process> _ProcList = new List<Process>();
public void ConvertWavToMp3 (TempFile srcFile, string title, Action<TempFile, Exception> complete)
{
// .. skipped ..
proc.Exited += delegate(object sender, EventArgs e) {
lock (this) {
_ProcList.Remove(proc);
}
proc.Dispose();
srcFile.Delete();
complete(dstFile, null);
};
proc.Start();
lock (this) {
_ProcList.Add(proc);
}
}
}
只要调用者持有LameConverter的引用,我就不再需要担心GC了。
应用程序中任何没有根的对象都是垃圾收集的候选对象。为了确保你的回调触发,你需要找到一个地方来存储对proc
的引用,否则你会有未定义的行为。
在这种情况下,一个选项是返回一个封装proc
的对象,而不通过公共接口公开它。不幸的是,在您的情况下,您必须泄漏一点底层实现的ConvertWavToMp3
的调用者,以确保所需的行为发生。
这里是一个可以工作的替代代码示例。但是,它将在进程执行时阻止对ConvertWavToMp3(…)的调用。可能不是你想要的。
public void ConvertWavToMp3 (TempFile srcFile, string title, Action<TempFile, Exception> complete)
{
var argument_fmt = "-S --resample 16 --tt {0} --add-id3v2 {1} {2}";
var dstFile = new TempFile(Path.GetTempFileName());
var proc = new System.Diagnostics.Process ();
proc.EnableRaisingEvents = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = "lame";
proc.StartInfo.Arguments = String.Format (argument_fmt,
title,
srcFile.Path,
dstFile.Path);
using(var wh = new System.Threading.ManualResetEvent(false))
{
proc.Exited += delegate(object sender, EventArgs e) {
proc.WaitForExit();
srcFile.Delete();
complete(dstFile, null);
wh.Set();
};
proc.Start();
wh.WaitOne();
}
}
就像我说的,这可能不是你想要的,除非你在一个控制台应用程序中。如果你在一个GUI应用程序中,保持对你的进程的引用。
public class MyForm : Form
{
// other form stuff
private System.Diagnostics.Process _encoderProc;
private void doEncode_Click(object sender, EventArgs e)
{
var argument_fmt = "-S --resample 16 --tt {0} --add-id3v2 {1} {2}";
var dstFile = new TempFile(Path.GetTempFileName());
var proc = new System.Diagnostics.Process ();
proc.EnableRaisingEvents = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.FileName = "lame";
proc.StartInfo.Arguments = String.Format (argument_fmt,
title,
srcFile.Path,
dstFile.Path);
proc.Exited += delegate(object sender, EventArgs e) {
proc.WaitForExit();
srcFile.Delete();
this.BeginInvoke((MethodInvoker)delegate {
// INSERT CODE HERE: your UI-related stuff that you want to do with dstFile
this._encoderProc = null;
});
};
proc.Start();
this._encoderProc = proc;
}
}
注意BeginInvoke(...)
的使用。如果你要做与UI相关的事情,它需要在UI线程上,而Exited
事件不会在UI线程上触发。