C#?winform?窗體控件跨線程訪問的實現(xiàn)
在寫計算機網(wǎng)絡課設的時候,遇到一個需求:建立的服務器需要一直監(jiān)聽來自某個端口的連接請求。一開始看到我想這不是很簡單嗎,正好剛剛學習了線程有關的知識,直接開個新線程掛在后臺執(zhí)行不就行了,抱著這樣簡單的想法,一堆錯誤接踵而至。
首先出現(xiàn)的問題就是窗體控件的跨線程訪問,會出現(xiàn)這樣的一個報錯信息:System.InvalidOperationException:“線程間操作無效: 從不是創(chuàng)建控件“textBox1”的線程訪問它。”
去找了找解決方案,其中最簡單的是這樣的一行代碼:
Control.CheckForIllegalCrossThreadCalls = false;
這行代碼只需要加到需要進行控制的控件那里,就會讓程序忽略線程的危險操作。
這樣確實就解決了問題,但是這是一個全局的靜態(tài)變量,如果在其他地方不小心修改了這一變量,每個控件就又會崩掉。所以,正統(tǒng)的解決方法應該是使用委托。
什么是委托?C#的委托類似于C++的函數(shù)指針,是一個可以批量執(zhí)行函數(shù)的容器。我們知道這個問題是發(fā)生在跨線程訪問中,那么我們?nèi)绻尯瘮?shù)調(diào)用發(fā)生在創(chuàng)建這個控件的線程中,那么就不是跨線程訪問,也就不存在這個問題了,因此我們使用this.invoke(delegrate)來使用委托。
來解釋這種解決方法,我們設想一個簡單的情景:我們需要在一個文本框中實時的更新當前時間,我們希望同時還可以在窗體中進行別的操作。
第一時間想到:
private void Form1_Load(object sender, EventArgs e) { while(true) { string time = DateTime.Now.ToString(); textBox1.Text = time; Thread.Sleep(1000); } }
這個顯然是錯誤的,甚至連窗口都跳不出來,更別提還可以在窗口中執(zhí)行其他操作。因為這個while(true)循環(huán)堵死了這個窗體的加載,根本加載不出來,會一直停留在加載這個環(huán)節(jié)。
然后就應該想到使用新開線程來解決這個問題了:
private void Form1_Load(object sender, EventArgs e) { // Control.CheckForIllegalCrossThreadCalls = false; // 如果加上上面這行代碼,就可以運行了,但是只是忽略了線程安全性 Thread timeShower = new Thread(ShowTime); timeShower.IsBackground = true; timeShower.Start(); } private void ShowTime() { while(true) { string time = DateTime.Now.ToString(); textBox1.Text = time; } }
很可惜,線程的第一次使用報錯了!跨線程訪問了textbox控件,這不安全。
接著,我們想使用委托的相關知識來解決這個問題:
private void Form1_Load(object sender, EventArgs e) { // 這個線程傳遞的是我們使用委托的那個函數(shù) Thread timeShower = new Thread(Textbox1ShowTime); timeShower.IsBackground = true; timeShower.Start(); } private delegate void TextBox1Delegrate(); // 聲明了一個無參無返回的委托 private void Textbox1ShowTime() { // 實例化委托,傳遞實際需要調(diào)用的ShowTime函數(shù) TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime); while (true) { this.Invoke(newDelegrate); // 這里的this代指Form1 Thread.Sleep(1000); } } private void ShowTime() { string time = DateTime.Now.ToString(); textBox1.Text = time; }
好的,我們已經(jīng)基本上完成了我們設想的情景。但是,我們能否一步到位,將while循環(huán)和Thread.Sleep(1000) 放到ShowTime函數(shù)中,然后直接使用委托啟動ShowTime,就可以直接得到實時刷新的時間呢?
如果你和我一樣聰明,估計會這么想:
private void Form1_Load(object sender, EventArgs e) { Thread timeShower = new Thread(Textbox1ShowTime); timeShower.IsBackground = true; timeShower.Start(); } private delegate void TextBox1Delegrate(); private void Textbox1ShowTime() { TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime); this.Invoke(newDelegrate); } private void ShowTime() { while (true) { string time = DateTime.Now.ToString(); textBox1.Text = time; Thread.Sleep(1000); } }
好的,恭喜你,獲得了一個無響應卡死的窗口!這是為什么呢?首先我們要知道this.Invoke(function)的原理是將這個function發(fā)送到創(chuàng)建this的這個線程,在這里也就是主線程中執(zhí)行,因此來規(guī)避跨線程問題。但是這里,我們傳了一個永真循環(huán)進去,其實和我們一開始想到的那個代碼沒有本質(zhì)上的區(qū)別,能顯現(xiàn)出窗口只是因為我們使用了委托(那還是進步了,恭喜我們)。
那么應該怎么樣才能實現(xiàn)一步到位的解決這個實時顯示時間的問題呢?
好,其實我也實現(xiàn)不了一步到位,如果有大神會的,一定要在評論區(qū)教教我QAQ
但是可以實現(xiàn)一個兩重外包,然后實現(xiàn)直接調(diào)用委托實時顯示時間。究其根本,只要不把永真的while循環(huán)放到主線程中執(zhí)行,就可以了,所以這兩重外包就是把while單獨分離了(其實上面那種實現(xiàn)方法也是同樣的思路,分離了永真while這個討厭的東西)
private void Form1_Load(object sender, EventArgs e) { Thread timeShower = new Thread(Textbox1ShowTime); timeShower.IsBackground = true; timeShower.Start(); } private delegate void TextBox1Delegrate(); private void Textbox1ShowTime() { TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime); newDelegrate.Invoke(); } private void ShowTime() { while (true) { TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime1); this.Invoke(newDelegrate); Thread.Sleep(1000); } } private void ShowTime1() { string time = DateTime.Now.ToString(); textBox1.Text = time; }
同樣的,順著分離這個思路走下去其實還可以進一步精簡我們的代碼。
這邊要提到一個屬性 textBox1.InvokeRequired。這個屬性是bool類型,他表示獲取一個值,該值指示調(diào)用方在對控件進行方法調(diào)用時是否因為調(diào)用的方法于不在創(chuàng)建控件的線程中,是否必須調(diào)用 Invoke 方法。
對于有許多控件和線程需要進行操作的情況而言,這個屬性可以非常方便的確定是否需要使用Invoke()方法進行委托的調(diào)用
對于我們這個從子線程Textbox1ShowTime()進去的ShowTime而言,這個屬性永遠是真的;而對于我們使用this.Invoke(newDelegrate)使用的ShowTime而言,它相當于是在主線程中執(zhí)行這個函數(shù),從而實現(xiàn)了狀態(tài)的分離。因此我們可以通過if(textBox1.InvokeRequired)實現(xiàn)將ShowTime1放到ShowTime中實現(xiàn)
private void Form1_Load(object sender, EventArgs e) { Thread timeShower = new Thread(Textbox1ShowTime); timeShower.IsBackground = true; timeShower.Start(); } private delegate void TextBox1Delegrate(); private void Textbox1ShowTime() { TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime); newDelegrate.Invoke(); } private void ShowTime() { if (textBox1.InvokeRequired) // 從new Thread(Textbox1ShowTime)調(diào)用的委托一定會進這里 { while (true) { TextBox1Delegrate newDelegrate = new TextBox1Delegrate(ShowTime); this.Invoke(newDelegrate); // 這個相當于是在主線程調(diào)用委托 Thread.Sleep(1000); // 注意這個sleep不能放到else中,不然會無限的休眠 } } else // 從主線程調(diào)用的委托一定會進到這里 { string time = DateTime.Now.ToString(); textBox1.Text = time; } }
OK,針對這個跨線程訪問的例子暫時我只能想這么多了,有錯誤的話大佬們不靈賜教啊QAQ
到此這篇關于C# winform 窗體控件跨線程訪問的實現(xiàn)的文章就介紹到這了,更多相關C# winform跨線程訪問內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
c#連接sqlserver數(shù)據(jù)庫、插入數(shù)據(jù)、從數(shù)據(jù)庫獲取時間示例
這篇文章主要介紹了c#連接sqlserver數(shù)據(jù)庫、插入數(shù)據(jù)、從數(shù)據(jù)庫獲取時間示例,需要的朋友可以參考下2014-05-05一個基于C#開發(fā)的Excel轉(zhuǎn)Json工具使用教程
JSON吸引了工具構建者的注意,它們開發(fā)了用于重新格式化、驗證和解析JSON的眾多工具,這不足為奇,下面這篇文章主要給大家介紹了一個基于C#開發(fā)的Excel轉(zhuǎn)Json工具的相關資料,需要的朋友可以參考下2022-11-11