C#新开一个线程取到数据,如何更新到主线程UI上面
一:问题
之前有被面试官问过,在WinForm中,要去网络上获取数据,由于网络环境等原因,不能很快的完成,因此会发生进程阻塞,造成主进程假死的现象,需要怎么解决?
二:思路
因此,往往是新建一个线程,让他执行耗时的操作,主线程管理用户界面,不会出现UI假死的情况,但是通过线程获取到的数据如何更新回主进程的UI上呢?这是另外一个问题
三:如下例子
我们发现如果直接在线程里更新UI会报错,报“从不是创建控件lable1的线程访问它”,为什么会报这个错呢?这个问题就是跨线程访问控件问题,窗体上的控件只允许创建它们的线程访问,也就是主线程(UI线程),如果非主线程访问则会发生异常我们看到会报错,且这个错误是“从不是创建控件lable1的线程访问它”
TestClass.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace WindowsFormsApplication1 9 { 10 public class TestClass 11 { 12 //声明一个delegate(委托)类型:TestDelegate,该类型可以搭载返回值为空,参数只有一个(long型)的方法。 13 public delegate void TestDelegate(long i); 14 15 //声明一个TestDelegate类型的对象。该对象代表了返回值为空,参数只有一个(long型)的方法。它可以搭载N个方法。 16 public TestDelegate mainThread; 17 18 /// 测试方法 19 /// <summary> 20 /// </summary> 21 public void TestFunction() 22 { 23 long i = 0; 24 while (true) 25 { 26 i++; 27 mainThread(i); //调用委托对象 28 Thread.Sleep(1000); //线程等待1000毫秒 29 } 30 } 31 } 32 }
Program.cs
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading; 9 using System.Threading.Tasks; 10 using System.Windows.Forms; 11 12 namespace WindowsFormsApplication1 13 { 14 public partial class Form1 : Form 15 { 16 public Form1() 17 { 18 InitializeComponent(); 19 } 20 21 private void button1_Click(object sender, EventArgs e) 22 { 23 //创建TestClass类的对象 24 TestClass testClass = new TestClass(); 25 26 //在testclass对象的mainThread(委托)对象上搭载两个方法,在线程中调用mainThread对象时相当于调用了这两个方法。 27 testClass.mainThread = new TestClass.TestDelegate(RefreshLabMessage1); 28 testClass.mainThread += new TestClass.TestDelegate(RefreshLabMessage2); 29 30 //创建一个无参数的线程,这个线程执行TestClass类中的TestFunction方法。 31 Thread testClassThread = new Thread(new ThreadStart(testClass.TestFunction)); 32 //启动线程,启动之后线程才开始执行 33 testClassThread.Start(); 34 } 35 36 /// <summary> 37 /// 在界面上更新线程执行次数 38 /// </summary> 39 /// <param name="i"></param> 40 private void RefreshLabMessage1(long i) 41 { 42 //判断该方法是否被主线程调用,也就是创建labMessage1控件的线程,当控件的InvokeRequired属性为ture时,说明是被主线程以外的线程调用。如果不加判断,会造成异常 43 if (this.labMessage1.InvokeRequired) 44 { 45 //再次创建一个TestClass类的对象 46 TestClass testclass = new TestClass(); 47 //为新对象的mainThread对象搭载方法 48 testclass.mainThread = new TestClass.TestDelegate(RefreshLabMessage1); 49 //this指窗体,在这调用窗体的Invoke方法,也就是用窗体的创建线程来执行mainThread对象委托的方法,再加上需要的参数(i) 50 this.Invoke(testclass.mainThread, new object[] { i }); 51 } 52 else 53 { 54 labMessage1.Text = i.ToString(); 55 } 56 } 57 58 59 /// <summary> 60 /// 在界面上更新线程执行次数 61 /// </summary> 62 /// <param name="i"></param> 63 private void RefreshLabMessage2(long i) 64 { 65 //同上 66 if (this.labMessage2.InvokeRequired) 67 { 68 //再次创建一个TestClass类的对象 69 TestClass testclass = new TestClass(); 70 //为新对象的mainThread对象搭载方法 71 testclass.mainThread = new TestClass.TestDelegate(RefreshLabMessage2); 72 //this指窗体,在这调用窗体的Invoke方法,也就是用窗体的创建线程来执行mainThread对象委托的方法,再加上需要的参数(i) 73 this.Invoke(testclass.mainThread, new object[] { i }); 74 } 75 else 76 { 77 labMessage2.Text = i.ToString(); 78 } 79 } 80 } 81 }
执行效果
旧书不厌百回读,熟读深思子自知。