在多線程中挪用winform窗體控件的完成辦法。本站提示廣大學習愛好者:(在多線程中挪用winform窗體控件的完成辦法)文章只能為提供參考,不一定能成為您想要的結果。以下是在多線程中挪用winform窗體控件的完成辦法正文
本文實例講述了在C#中完成多線程中挪用winform窗體控件的辦法,關於C#法式設計的進修有著很好的自創參考價值。詳細辦法以下:
起首,因為Windows窗體控件實質上不是線程平安的。是以假如有兩個或多個線程過度操作某一控件的狀況(set value),則能夠會迫使該控件進入一種紛歧致的狀況。還能夠湧現其他與線程相干的 bug,包含爭用和逝世鎖的情形。因而在調試器中運轉運用法式時,假如創立某控件的線程以外的其他線程試圖挪用該控件,則調試器會激發一個 InvalidOperationException
本文用一個很簡略的示例來說解這個成績(在窗體上放一個TextBox和一個Button,點擊Button後,在新建的線程中設置TextBox的值)
處理方法一: 封閉該異常檢測的方法來防止異常的湧現
經由測試發明此種辦法固然防止了異常的拋出,然則其實不能包管法式運轉成果的准確性 (好比多個線程同時設置TextBox1的Text時,很難估計終究TextBox1的Text是甚麼)
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; namespace winformTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); Control.CheckForIllegalCrossThreadCalls = false;//這一行是症結 } private void button1_Click(object sender, EventArgs e) { SetTextBoxValue(); } void SetTextBoxValue() { TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method1"); ThreadStart TS = new ThreadStart(tbsv.SetText); Thread T = new Thread(TS); T.Start(); } class TextBoxSetValue { private TextBox _TextBox ; private string _Value; public TextBoxSetValue(TextBox TxtBox, String Value) { _TextBox = TxtBox; _Value = Value; } public void SetText() { _TextBox.Text = _Value; } } } }
處理方法二:經由過程拜托平安挪用
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace winformTest { public partial class Form2 : Form { public Form2() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { SetTextBoxValue(); } private delegate void CallSetTextValue(); //經由過程拜托挪用 void SetTextBoxValue() { TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method2"); if (tbsv.TextBox.InvokeRequired) { CallSetTextValue call = new CallSetTextValue(tbsv.SetText); tbsv.TextBox.Invoke(call); } else { tbsv.SetText(); } } class TextBoxSetValue { private TextBox _TextBox; private string _Value; public TextBoxSetValue(TextBox TxtBox, String Value) { _TextBox = TxtBox; _Value = Value; } public void SetText() { _TextBox.Text = _Value; } public TextBox TextBox { set { _TextBox = value; } get { return _TextBox; } } } } }
第三處理方法:應用BackgroundWorker控件
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; namespace winformTest { public partial class Form3 : Form { public Form3() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { using (BackgroundWorker bw = new BackgroundWorker()) { bw.RunWorkerCompleted += SetTextBoxValue; bw.RunWorkerAsync(); } } void SetTextBoxValue(object sender, RunWorkerCompletedEventArgs e) { TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method3"); tbsv.SetText(); } class TextBoxSetValue { private TextBox _TextBox; private string _Value; public TextBoxSetValue(TextBox TxtBox, String Value) { _TextBox = TxtBox; _Value = Value; } public void SetText() { _TextBox.Text = _Value; } } } }
用戶不愛好反響慢的法式。在履行耗時較長的操作時,應用多線程是明智之舉,它可以進步法式 UI 的呼應速度,使得一切運轉顯得更加疾速。在 Windows 中停止多線程編程已經是 C++ 開辟人員的專屬特權,然則如今,可使用一切兼容 Microsoft .NET 的說話來編寫。
不外Windows 窗體系統構造對線程應用制訂了嚴厲的規矩。假如只是編寫單線程運用法式,則沒需要曉得這些規矩,這是由於單線程的代碼弗成能違背這些規矩。但是,一旦采取多線程,就須要懂得 Windows 窗體中最主要的一條線程規矩:除少少數的破例情形,不然都不要在它的創立線程之外的線程中應用控件的任何成員。本規矩的破例情形有文檔解釋,但如許的情形異常少。這實用於其類派生自 System.Windows.Forms.Control 的任何對象,個中簡直包含 UI 中的一切元素。一切的 UI 元素(包含表單自己)都是從 Control 類派生的對象。另外,這條規矩的成果是一個被包括的控件(如,包括在一個表單中的按鈕)必需與包括它控件位處於統一個線程中。也就是說,一個窗口中的一切控件屬於統一個 UI 線程。現實中,年夜部門 Windows 窗體運用法式終究都只要一個線程,一切 UI 運動都產生在這個線程上。這個線程平日稱為 UI 線程。這意味著您不克不及挪用用戶界面中隨意率性控件上的任何辦法,除非在該辦法的文檔解釋中指出可以挪用。該規矩的破例情形(總有文檔記載)異常少並且它們之間關系也不年夜。請留意,以下代碼長短法的:
private Thread myThread; private void Form1_Load(object sender, EventArgs e) { myThread = new Thread(new ThreadStart(RunsOnWorkerThread)); myThread.Start(); } private void RunsOnWorkerThread() { label1.Text = "myThread線程挪用UI控件"; }
假如您在 .NET Framework 1.0 版本中測驗考試運轉這段代碼,或許會幸運運轉勝利,或許初看起來是如斯。這就是多線程毛病中的重要成績,即它們其實不會立刻浮現出來。乃至當湧現了一些毛病時,在第一次演示法式之前一切看起來也都很正常。但不要弄錯 — 我適才顯示的這段代碼顯著違背了規矩,而且可以預感,任何抱願望於“試運轉時優越,應當就沒有成績”的人期近將到來的調試期是會支付繁重價值的。
上面我們來看看有哪些辦法可以處理這一成績。
1、System.Windows.Forms.MethodInvoker 類型是一個體系界說的拜托,用於挪用不帶參數的辦法。
private Thread myThread; private void Form1_Load(object sender, EventArgs e) { myThread = new Thread(new ThreadStart(RunsOnWorkerThread)); myThread.Start(); } private void RunsOnWorkerThread() { MethodInvoker mi = new MethodInvoker(SetControlsProp); BeginInvoke(mi); } private void SetControlsProp() { label1.Text = "myThread線程挪用UI控件"; }
2、直接用System.EventHandle(可帶參數)
private Thread myThread; private void Form1_Load(object sender, EventArgs e) { myThread = new Thread(new ThreadStart(RunsOnWorkerThread)); myThread.Start(); } private void RunsOnWorkerThread() { //DoSomethingSlow(); string pList = "myThread線程挪用UI控件"; label1.BeginInvoke(new System.EventHandler(UpdateUI), pList); } //直接用System.EventHandler,沒有需要自界說拜托 private void UpdateUI(object o, System.EventArgs e) { //UI線程設置label1屬性 label1.Text = o.ToString() + "勝利!"; }
3、包裝 Control.Invoke
固然第二個辦法中的代碼處理了這個成績,但它相當繁瑣。假如幫助線程願望在停止時供給更多的反應信息,而不是簡略地給出“Finished!”新聞,則 BeginInvoke過於龐雜的應用辦法會使人生畏。為了轉達其他新聞,例如“正在處置”、“一切順遂”等等,須要想法向 UpdateUI 函數傳遞一個參數。能夠還須要添加一個進度欄以進步反應才能。這麼屢次挪用 BeginInvoke 能夠招致幫助線程受該代碼安排。如許不只會形成未便,並且斟酌到幫助線程與 UI 的調和性,如許設計也欠好。對這些停止剖析以後,我們以為包裝函數可以處理這兩個成績。
private Thread myThread; private void Form1_Load(object sender, EventArgs e) { myThread = new Thread(new ThreadStart(RunsOnWorkerThread)); myThread.Start(); } private void RunsOnWorkerThread() { ////DoSomethingSlow(); for (int i = 0; i < 100; i++) { ShowProgress( Convert.ToString(i)+"%", i); Thread.Sleep(100); } } public void ShowProgress(string msg, int percentDone) { // Wrap the parameters in some EventArgs-derived custom class: System.EventArgs e = new MyProgressEvents(msg, percentDone); object[] pList = { this, e }; BeginInvoke(new MyProgressEventsHandler(UpdateUI), pList); } private delegate void MyProgressEventsHandler(object sender, MyProgressEvents e); private void UpdateUI(object sender, MyProgressEvents e) { lblStatus.Text = e.Msg; myProgressControl.Value = e.PercentDone; } public class MyProgressEvents : EventArgs { public string Msg; public int PercentDone; public MyProgressEvents(string msg, int per) { Msg = msg; PercentDone = per; } }
ShowProgress 辦法對將挪用引向准確線程的任務停止封裝。這意味著幫助線程代碼不再擔憂須要過量存眷 UI 細節,而只需按期挪用 ShowProgress 便可。
假如我供給一個設計為可從任何線程挪用的公共辦法,則完整有能夠或人會從 UI 線程挪用這個辦法。在這類情形下,沒需要挪用 BeginInvoke,由於我曾經處於准確的線程中。挪用 Invoke 完整是糟蹋時光和資本,不如直接挪用恰當的辦法。為了不這類情形,Control 類將地下一個稱為 InvokeRequired 的屬性。這是“只限 UI 線程”規矩的另外一個破例。它可從任何線程讀取,假如挪用線程是 UI 線程,則前往假,其他線程則前往真。這意味著我可以按以下方法修正包裝:
public void ShowProgress(string msg, int percentDone) { if (InvokeRequired) { // As before //... } else { // We're already on the UI thread just // call straight through. UpdateUI(this, new MyProgressEvents(msg,PercentDone)); } }
線程翻開窗體的成績:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace WindowsApplication36 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } private void button1_Click(object sender, EventArgs e) { new System.Threading.Thread(new System.Threading.ThreadStart(invokeShow)).Start(); } public void invokeShow() { Form f1 = new Form(); this.Invoke(new System.EventHandler(this.showForm), new object[] { f1, null }); } public void showForm(object sender,EventArgs e) { Form f1 = sender as Form; f1.ShowDialog(); } } }