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