在多線程中挪用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();
}
}
}