我在其他问题中提到过,我正在使用进度条等工作ffmpeg解析器。C#有时会在运行线程时冻结表单
以下问题不是100%可重现。当我用我的程序转换视频文件时,大部分时间都可以。它可以在较小的文件(大约100MB)上完美工作,但在较大的文件(大约1GB)上,有时候gui会冻结,但ffmpeg正在运行(调试器仍然从ffmpeg获取输出并显示它,因此表单线程不冻结,只有控制台的GUI)
我也意识到,ffmpeg所做的工作不会造成任何可能导致死机的错误。
如果我在调试环境外运行它,表格也会冻结。
解析在额外的线程上处理ffmpeg异步的输出。
我认为这个线程冻结了,并且主程序在ffmpeg完成后会挂起,因为解析器得到一个command == null
以及主程序将被传递完成转换。我实际上不能说这个,因为atm我不能重现冻结,而且最后一次忘记提及调试器是否输出"======== #"
。
你可以找到源头(在调试文件夹中的ffmpeg)here作为ConvertApp.zip
进程之间的谈话:
// message directors for threading
private BlockingCollection<string> commandsForParser = new BlockingCollection<string>();
private BlockingCollection<string> commandsForMain = new BlockingCollection<string>();
// sending commands to threads
public void SendCommmandParser(string command)
{
commandsForParser.Add(command);
//Debug.WriteLine("P: " + command);
}
public void SendCommmandMain(string command)
{
commandsForMain.Add(command);
//Debug.WriteLine("M: " + command);
}
呼吁解析:
private void showParsedConsole()
{
ConsoleOutput dlg = new ConsoleOutput();
dlg.Show();
//...
while (true)
{
System.Windows.Forms.Application.DoEvents();
string command = commandsForParser.Take();
// if null, ffmpeg finished
if (command == null || command == "..break..")
{
SendCommmandMain("..break..");
dlg.Close();
break;
}
if (dlg.toClose)
{
SendCommmandMain("..cancel..");
dlg.Close();
break;
}
else if (command != null)
{
//... actualizing form (output, progress things)
}
else
dlg.addMessage("\n");
}
}
出发转换:
public string RunExternalExe(string info, string filename, string arguments = null)
{
// parse console in new thread
var thread = new Thread(showParsedConsole);
thread.Start();
//...
#region init process to run
var process = new Process();
process.StartInfo.FileName = filename;
if (!string.IsNullOrEmpty(arguments))
{
process.StartInfo.Arguments = arguments;
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
var stdOutput = new StringBuilder();
var errOutput = new StringBuilder();
#endregion
#region redirect stdOut/stdError
process.OutputDataReceived += (sender, args) =>
{
SendCommmandParser(args.Data);
stdOutput.Append(args.Data + "\n");
Debug.WriteLine("S: " + args.Data);
};
process.ErrorDataReceived += (sender, args) =>
{
SendCommmandParser(args.Data);
errOutput.Append(args.Data + "\n");
// if the form is freezing, the debugger will still output these
Debug.WriteLine("E: " + args.Data);
};
#endregion
#region run process
try
{
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
while (!process.HasExited)
{
System.Windows.Forms.Application.DoEvents();
string command = commandsForMain.Take();
if (command == "..cancel..")
{
Debug.WriteLine("============== 1");
process.Kill();
while (process != null && !process.HasExited)
{
//wait
}
// return if canceled to provide excetion (process == null)
return "C";
}
if (command == "..break..")
{
Debug.WriteLine("============== 2");
process.WaitForExit();
break;
}
/*...*/
}
Debug.WriteLine("============== 3");
SendCommmandParser("..break..");
}
catch
{
}
#endregion
Debug.WriteLine("============== 4");
//... handling OK, CANCEL, ERROR
}
任何人都可以找到一个结构性问题,这会导致这种冻结? (实际上我运行2个转换没有任何错误,但没有改变代码)
感谢您的帮助。
〜ADD〜 现在我得到了一个冻结运行,调试器不会输出"============== #"
,所以解析线程确实冻结了...但是为什么?
如果你调用'Application.DoEvents()'*和*使用一个线程,你*必须*做错了什么。除非设计出现问题,否则绝对不应该调用'Application.DoEvents()'。所有的工作应该在线程中完成。用户界面应始终保持响应。而且你不能调用'Application.DoEvents()' - 我有提到吗? ;) –
,我该如何重组?如果我在'showParsedConsole()'中取消了'Application.DoEvents()'的注释,那么ParsingForm会冻结。而且工作人员不应该反过来调用ParsingForm,因为表单只是它的工作显示。问题是,工作人员必须等到ffmpeg准备就绪后,我实际上不知道如何重新组织... – gu471
它看起来像我整个'RunExternalExe()'应该从一个单独的线程运行。使用[BackgroundWorker组件](http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx)在单独的线程中运行它可能是最简单的。 –