c#有时会在运行线程时冻结表单

本文关键字:线程 冻结 运行 表单 | 更新日期: 2023-09-27 18:14:01

我在另一个问题中提到,我正在研究一个带有progressbar等的ffmpeg解析器。

下面的问题不是100%可重现的。当我用我的程序转换视频文件时,大多数时候都没问题。它在较小的文件(~100MB输出)上工作得很好,但是在较大的文件(~1GB输出)上,有时gui会冻结,但是ffmpeg正在工作(调试器仍然从ffmpeg获取输出并显示它,因此表单线程不会冻结,只有控制台的gui)

我还意识到ffmpeg完成了它必须完成的工作,没有任何可能引起冻结的错误。

如果我在Debug-Environment之外运行它,窗体也会冻结。

解析工作在一个额外的线程上处理ffmpeg async的输出。

我认为这个线程冻结,并且主程序将在ffmpeg完成后挂起,因为解析器获得一个command == null,其中主程序将被告知转换完成。我实际上不能这么说,因为我不能重现冻结,而且上次我忘记提到调试器是否输出"======== #"

您可以在这里找到源代码(在调试文件夹中使用ffmpeg),即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转换没有任何错误,但没有改变代码)

谢谢你的帮助。

~ ~现在我得到了一个冻结运行,调试器不输出"============== #",所以解析线程确实冻结了…但是为什么呢?

c#有时会在运行线程时冻结表单

多亏了Matthew, Application.DoEvents()似乎真的提高了冻结。我不知道为什么,但我完全重组了代码,所以主gui将转换请求发送到BlockingCollection<>,这将由一个新线程完成。

为了保持窗口不冻结,我使用main-gui线程初始化它,并使用Invoke s从工作线程更新它。

重新组织另一个项目是一项艰苦的工作,因为主要形式是调用不同的转换,并为其成功设置条件,但实际上它是有效的。

对于未来,我知道:只需调用密集的工作,只调用一个类,使以后的并行化更容易;)