問題
之前有被面試官問過,在WinForm中,要去網絡上獲取數據,由於網絡環境等原因,不能很快的完成,因此會發生進程阻塞,造成主進程假死的現象,需要怎麼解決?思路
因此,往往是新建一個線程,讓他執行耗時的操作,主線程管理用戶界面,不會出現UI假死的情況,但是通過線程獲取到的數據如何更新回主進程的UI上呢?這是另外一個問題- 如下例子
我們發現如果直接在線程裏更新UI會報錯,報“從不是創建控件lable1的線程訪問它”,爲什麼會報這個錯呢?這個問題就是跨線程訪問控件問題,窗體上的控件只允許創建它們的線程訪問,也就是主線程(UI線程),如果非主線程訪問則會發生異常我們看到會報錯,且這個錯誤是“從不是創建控件lable1的線程訪問它”
TestClass.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WindowsFormsApplication1
{
public class TestClass
{
//聲明一個delegate(委託)類型:TestDelegate,該類型可以搭載返回值爲空,參數只有一個(long型)的方法。
public delegate void TestDelegate(long i);
//聲明一個TestDelegate類型的對象。該對象代表了返回值爲空,參數只有一個(long型)的方法。它可以搭載N個方法。
public TestDelegate mainThread;
/// 測試方法
/// <summary>
/// </summary>
public void TestFunction()
{
long i = 0;
while (true)
{
i++;
mainThread(i); //調用委託對象
Thread.Sleep(1000); //線程等待1000毫秒
}
}
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//創建TestClass類的對象
TestClass testClass = new TestClass();
//在testclass對象的mainThread(委託)對象上搭載兩個方法,在線程中調用mainThread對象時相當於調用了這兩個方法。
testClass.mainThread = new TestClass.TestDelegate(RefreshLabMessage1);
testClass.mainThread += new TestClass.TestDelegate(RefreshLabMessage2);
//創建一個無參數的線程,這個線程執行TestClass類中的TestFunction方法。
Thread testClassThread = new Thread(new ThreadStart(testClass.TestFunction));
//啓動線程,啓動之後線程纔開始執行
testClassThread.Start();
}
/// <summary>
/// 在界面上更新線程執行次數
/// </summary>
/// <param name="i"></param>
private void RefreshLabMessage1(long i)
{
//判斷該方法是否被主線程調用,也就是創建labMessage1控件的線程,當控件的InvokeRequired屬性爲ture時,說明是被主線程以外的線程調用。如果不加判斷,會造成異常
if (this.labMessage1.InvokeRequired)
{
//再次創建一個TestClass類的對象
TestClass testclass = new TestClass();
//爲新對象的mainThread對象搭載方法
testclass.mainThread = new TestClass.TestDelegate(RefreshLabMessage1);
//this指窗體,在這調用窗體的Invoke方法,也就是用窗體的創建線程來執行mainThread對象委託的方法,再加上需要的參數(i)
this.Invoke(testclass.mainThread, new object[] { i });
}
else
{
labMessage1.Text = i.ToString();
}
}
/// <summary>
/// 在界面上更新線程執行次數
/// </summary>
/// <param name="i"></param>
private void RefreshLabMessage2(long i)
{
//同上
if (this.labMessage2.InvokeRequired)
{
//再次創建一個TestClass類的對象
TestClass testclass = new TestClass();
//爲新對象的mainThread對象搭載方法
testclass.mainThread = new TestClass.TestDelegate(RefreshLabMessage2);
//this指窗體,在這調用窗體的Invoke方法,也就是用窗體的創建線程來執行mainThread對象委託的方法,再加上需要的參數(i)
this.Invoke(testclass.mainThread, new object[] { i });
}
else
{
labMessage2.Text = i.ToString();
}
}
}
}
執行效果