C#多线程调用Invoke后窗体冻结问题求助
问题:多线程更新UI导致窗体冻结
我有一个仅包含多行文本框的C#窗体,需在窗体Load方法中创建3个工作线程,调用Contador类的contar方法。该方法循环10000次调用窗体类的函数,在文本框中打印线程编号与计数值。原本期望通过Invoke方法让UI操作在主线程执行、后台线程正常工作,但窗体出现冻结现象,附上代码寻求帮助。
主窗体类代码
namespace TestMultiHiloCSharp { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { Contador contadorClassOne = new(1, this); Contador contadorClassTwo = new(2, this); // 注:原代码仅创建2个线程,需求为3个 Task t1 = Task.Run(() => { contadorClassOne.contar(); }); Task t2 = Task.Run(() => { contadorClassTwo.contar(); }); } public void showMessage(String Mensaje) { this.Invoke(()=> { txtMultiLineTextBox.Text = Mensaje + "\r\n" + txtMultiLineTextBox.Text; }); } } }
Contador类代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestMultiHiloCSharp { internal class Contador { int numHilo; Form1 thisForm; public Contador(int numHilo,Form1 thisForm) { this.numHilo = numHilo; this.thisForm = thisForm; } public void contar(){ int cont = 0; while (cont < 100) { thisForm.showMessage("this is the theread: " + this.numHilo + " and the number: " + cont); cont++; } } } }
问题原因
你使用的Invoke是同步跨线程调用:后台线程会等待主线程完成UI更新后,才会继续执行下一次循环。当循环次数较多(比如10000次)时,主线程会被大量同步请求占满,无法处理窗体的其他消息(比如鼠标点击、窗口重绘),最终导致窗体冻结。
解决方法
1. 改用异步调用BeginInvoke
把showMessage里的Invoke换成BeginInvoke,后台线程无需等待UI更新完成即可继续执行,不会阻塞主线程:
public void showMessage(String Mensaje) { this.BeginInvoke(()=> { txtMultiLineTextBox.Text = Mensaje + "\r\n" + txtMultiLineTextBox.Text; }); }
2. 批量更新UI(针对10000次循环的优化)
如果循环次数是10000次,哪怕用BeginInvoke,10000次跨线程调用仍会给UI线程带来压力。可以在后台线程中缓存一批消息,再一次性更新到UI,减少跨线程调用次数:
修改Contador类的contar方法:
public void contar() { int cont = 0; StringBuilder sb = new StringBuilder(); while (cont < 10000) { sb.AppendLine($"线程{numHilo}:计数{cont}"); cont++; // 每100次批量更新一次UI if (cont % 100 == 0) { thisForm.showMessage(sb.ToString()); sb.Clear(); } } // 处理最后一批未满100条的消息 if (sb.Length > 0) { thisForm.showMessage(sb.ToString()); } }
3. 补充第三个线程(满足需求)
原代码只创建了2个线程,按照需求补充第三个:
private void Form1_Load(object sender, EventArgs e) { Contador contadorClassOne = new(1, this); Contador contadorClassTwo = new(2, this); Contador contadorClassThree = new(3, this); Task.Run(() => contadorClassOne.contar()); Task.Run(() => contadorClassTwo.contar()); Task.Run(() => contadorClassThree.contar()); }
4. 解耦线程类与窗体(可选优化)
直接让Contador持有窗体引用可能导致内存泄漏,改用委托解耦:
修改Contador类:
internal class Contador { int numHilo; private readonly Action<string> _updateUi; public Contador(int numHilo, Action<string> updateUi) { this.numHilo = numHilo; _updateUi = updateUi; } public void contar() { int cont = 0; StringBuilder sb = new StringBuilder(); while (cont < 10000) { sb.AppendLine($"线程{numHilo}:计数{cont}"); cont++; if (cont % 100 == 0) { _updateUi(sb.ToString()); sb.Clear(); } } if (sb.Length > 0) { _updateUi(sb.ToString()); } } }
窗体Load方法初始化:
private void Form1_Load(object sender, EventArgs e) { var contador1 = new Contador(1, showMessage); var contador2 = new Contador(2, showMessage); var contador3 = new Contador(3, showMessage); Task.Run(() => contador1.contar()); Task.Run(() => contador2.contar()); Task.Run(() => contador3.contar()); }
内容的提问来源于stack exchange,提问作者Aldahir Ruiz Valdez
相关产品推荐
相关产品推荐

