#winform#子控件刷新时,主界面卡死,要如何解决

2020-03-09 11:11:35 +08:00
 x537196

在做一个日志工具 GUI,主界面上有一个 richtext 控件输出日志信息,当日志疯狂输出时,主界面就卡死了,有什么的解决方案呢? 日志是启用一个线程,线程中 Process 调用 cmd,委托事件输出日志。日志追加方法如下。我是才看了 3 天的 C#,网上资料太少了点

        public void AppendConsole(string log)
        {

            if (string.IsNullOrWhiteSpace(log)) return;
            //在 UI 线程中执行
            consolebox.BeginInvoke(new Action(() =>
            {
                consolebox.AppendText(log);
                consolebox.AppendText(Environment.NewLine);//追加换行符
            }));

        }
2788 次点击
所在节点    Windows
17 条回复
tanranran
2020-03-09 13:03:55 +08:00
限流
blueboyggh
2020-03-09 13:41:46 +08:00
貌似需要用 backgroundworker 吧
mcdull619
2020-03-09 13:49:49 +08:00
别在 UI 线程中输出 , 创建子线程做这些操作 .
geelaw
2020-03-09 14:18:08 +08:00
首先,大量进行 AppendText 本来性能就不行,使用 #1 的思路,限制 append 的频率,一次 append 多条消息(先拼好再送去 AppendText )。另一个思路是使用性能更好的控件,例如这里完全没有体现为什么要用 RichText。

@mcdull619 #3 是强行背诵式回答问题,对 UI 的变化只能在 UI 线程上进行。
x537196
2020-03-09 15:12:12 +08:00
@geelaw 好的谢谢,如果不用这个控件, 选用什么控件好一点,因为我不太了解
x537196
2020-03-09 15:12:42 +08:00
@blueboyggh 试过了,也是会卡
Daming
2020-03-09 15:27:07 +08:00
一个最简单的

Task.Factory.StartNew(() =>
{
// 和 UI 无关的逻辑

this.Invoke(new Action(()=>{
//UI 逻辑操作
}));

}).ContinueWith(t =>
{
if (t.IsFaulted)
{
// 记录异常
}
});
x537196
2020-03-09 15:30:11 +08:00
@Daming 不是任务的问题,应该是 UI 刷新太频繁了
ysc3839
2020-03-09 15:32:35 +08:00
@geelaw > 对 UI 的变化只能在 UI 线程上进行。

这似乎是不一定的? Windows UI 的多线程限制好像不那么严格。
按照微软文档所说 https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage
If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure.
看上去 Windows 会自动切到目标线程执行,不需要自己处理的。
darktutu
2020-03-09 15:37:09 +08:00
最近搞过一次界面卡死的问题,你先降低更新的频率,不要那么频繁。
x537196
2020-03-09 15:42:34 +08:00
@darktutu 已经搞到 50ms 输出一次了
geelaw
2020-03-09 16:21:13 +08:00
@ysc3839 WinForm 默认情况下会对每个 UI 变化进行检查,如果当前线程不是建立该 UI 对象的线程则直接抛出异常。

你应该认为所有的 UI 对象都相当于一个 STA COM 对象,而 Windows 提供的 SendMessage 等 API 相当于是带有 marshalling 的,因此如果你尝试从另一个线程 SendMessage 到 UI 对象,就相当于你进行了正确的跨 apartment COM 调用。SetWindowText 最终也会变成 SendMessage,因此调用 Win32 API 会有正确的结果。

然而这样随意的编程方式很危险——因为 SendMessage 自己会进行消息处理,你的 WndProc 必须是 reentrant 才行,大多数人写出来的都不是。WinForm 的做法就是默认不允许跨线程操作,程序员需要显式表达线程切换——好习惯从最开始就要培养。
geelaw
2020-03-09 16:21:50 +08:00
@x537196 #5 最简单的想法是用不 rich 的 TextBox。
darktutu
2020-03-09 20:22:23 +08:00
private void button1_Click(object sender, EventArgs e)
{
var task = new Task(() =>
{
var index = 1;
while(true)
{
AppendConsole(index.ToString());
index++;
System.Threading.Thread.Sleep(50);
}
});
task.Start();
}
public void AppendConsole(string log)
{
if (string.IsNullOrWhiteSpace(log)) return;
//在 UI 线程中执行
richTextBox1.BeginInvoke(new Action(() =>
{
richTextBox1.AppendText(log);
richTextBox1.AppendText(Environment.NewLine);//追加换行符
}));

}

兄弟,我这么跑也不会卡死啊?
x537196
2020-03-10 11:59:30 +08:00
@geelaw 用 richtextbox 是要用颜色标记一些信息出来
x537196
2020-03-10 12:01:07 +08:00
@darktutu 我开始设置的 sleep 是 2,然后还有一个内容变更事件没有处理
neilq
2020-03-10 15:34:06 +08:00
存在缓存里,每隔多少秒输出到 ui,或者每满多少兆输出到 ui,或者多种策略结合着来

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/651122

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX