【问题标题】:C# sometimes freezing form when running a thread运行线程时 C# 有时会冻结表单
【发布时间】:2013-05-16 16:44:38
【问题描述】:

我在另一个问题中提到,我正在开发一个带有进度条等的 ffmpeg 解析器。

以下问题不是 100% 可重现的。当我用我的程序转换视频文件时,大多数时候一切正常。它在较小的文件(约 100MB 输出)上完美运行,但在较大的文件(约 1GB 输出)上,有时 gui 会冻结,但 ffmpeg 正在运行(调试器仍然从 ffmpeg 获取输出并显示它,所以表单线程确实'不要冻结,只有控制台的 gui)

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

如果我在调试环境之外运行,表单也会冻结。

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

我认为这个线程会冻结,并且主程序将在 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 次转换没有任何错误,但没有更改代码)

感谢您的帮助。

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

【问题讨论】:

  • 如果你调用Application.DoEvents()并且使用一个线程,你一定做错了什么。除非设计有问题,否则您绝对不需要致电Application.DoEvents()。所有的工作都应该在线程中完成。 UI 应该总是响应式的。而且你不能打电话给Application.DoEvents()——我提到过吗? ;)
  • 我该如何重新组织这个?如果我在 showParsedConsole() 中评价 Application.DoEvents(),则 ParsingForm 会冻结。并且工作人员应该调用 ParsingForm 而不是反过来,因为表单只是它工作的显示。问题是,工人必须等到 ffmpeg 准备好,而我实际上不知道如何重组......
  • 在我看来你的整个RunExternalExe() 应该从一个单独的线程运行。使用BackgroundWorker component 在单独的线程中运行它可能是最简单的。
  • 我想了一下,每当我看到问题时,主程序(gui)必须等待转换过程完成。在这个小程序中不惜一切代价,但在我为其开发它的程序中。而且我实际上认为不使用Application.DoEvents() 就无法保持gui 解冻。在我为其开发的程序中,主 gui 必须等待继续进行,直到完成所有转换。 ://

标签: c# multithreading user-interface freeze


【解决方案1】:

感谢 Matthew,Application.DoEvents() 似乎确实提高了冻结状态。 我不知道为什么,但我完全重组了代码,所以主 gui 将转换请求发送到 BlockingCollection&lt;&gt;,这将由一个新线程处理。

为了保持窗口未冻结,我使用 main-gui-thread 对其进行了初始化,并使用 Invokes 从工作线程对其进行了更新。

重组另一个项目很辛苦,因为主要形式是调用不同的转换,并附有成功的条件,但实际上它是有效的。

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

【讨论】:

    猜你喜欢
    • 2021-12-04
    • 1970-01-01
    • 2020-07-20
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    相关资源
    最近更新 更多