任運自在:執行緒(Thread)與委託(Invoke和BeginInvoke)和封裝
執行緒(Thread)與委託(Invoke和BeginInvoke)
這幾天專門玩執行緒與委託,到處查詢資料看,漸漸明白了用法、寫法和一些注意事項;
描述:
什麼是程序呢?當一個程式開始執行時,它就是一個程序,程序所指包括執行中的程式和程式所使用到的記憶體和系統資源。而一個程序又是由多個執行緒所組成的,執行緒是程式中的一個執行流,每個執行緒都有自己的專有暫存器(棧指標、程式計數器等),但程式碼區是共享的,即不同的執行緒可以執行同樣的函式。
Control.Invoke 方法 (Delegate) :在擁有此控制元件的基礎視窗控制代碼的執行緒上執行指定的委託。
Control.BeginInvoke 方法 (Delegate) :在建立控制元件的基礎控制代碼所線上程上非同步執行指定委託。
Control的Invoke和BeginInvoke的引數為delegate,委託的方法是在Control的執行緒上執行的,也就是我們平時所說的UI執行緒。
何時採用簡單歸納:
1、提高CPU的利用率,從而提高了程式的效率;
2、當程式執行會卡住軟體介面,為了使人不用焦慮等待時,採取執行緒與委託來處理,從而使軟體介面執行流暢;
3、處理大量資料時間較長(顯示一個進度條給介面)或不需要馬上得到結果反饋給軟體介面時;
注意事項:執行緒是各自獨立進行管理的,一個執行緒不能包含另一個執行緒;即:用Thread建立的執行緒是一個執行緒,Control是一個執行緒,這是兩個獨立的執行緒;委託則專屬於Control執行緒;至少目前我是這樣理解的,因此,初涉這個領域時不小心就會犯執行緒相互交涉而發生錯誤;更具體檢視微軟或網路上相關資料就不贅述了。
舉例是最生動的說明,看下面例子:
要執行的操作是計算一個文字控制元件中有多少個字元,包含多少回車數量:
private delegate void 數字委託(int 數字);
private void 計算字數(int 回車)
{
顯示控制元件.Text = "字數:" + 文字.TextLength.ToString() + ";";
顯示控制元件.Text += "其中包含" + 回車.ToString() + "回車數";
}
//請注意上面方法包含Control執行緒,而下面方法不包含Control執行緒,並註釋掉公共類傳遞引數,可自己除錯。
private void 計算回車數量(object 資料)
{
int 回車 = 0;
for (int i = 0, 數量 = 資料.ToString().Length; i < 數量; i++)
{
if (資料.ToString().Contains("\n")) 回車++;
if (資料.ToString().IndexOf("\n") + 1 < 資料.ToString().Length)
資料 = 資料.ToString().Substring(資料.ToString().IndexOf("\n") + 1);
else break;
if (!資料.ToString().Contains("\n")) break;
}
this.BeginInvoke(new 數字委託(計算字數), 回車);
}
private void 按鈕_Click(object sender, EventArgs e)
{
new Thread(new ParameterizedThreadStart(計算回車數量)).Start(文字控制元件.Text);
}
//宣告並執行建立的執行緒,同時傳遞引數,Start傳遞的是object型別
執行正常。
在private void 計算回車數量(object 資料)方法中用委託來返回計算結果,引數為delegate,在最上一行宣告,這樣就返回到Control執行緒。
假如不用委託而直接用:計算字數(回車);將提示錯誤,原因就是不同執行緒發生交涉。如下:
private void 計算回車數量(object 資料)
{
int 回車 = 0;
for (int i = 0, 數量 = 資料.ToString().Length; i < 數量; i++)
{
if (資料.ToString().Contains("\n")) 回車++;
if (資料.ToString().IndexOf("\n") + 1 < 資料.ToString().Length)
資料 = 資料.ToString().Substring(資料.ToString().IndexOf("\n") + 1);
else break;
if (!資料.ToString().Contains("\n")) break;
} 計算字數(回車);
}
宣告並執行執行緒語句不同寫法:
--------------------------------------------------用公共類傳遞
public class 共類 { public string 資料 { get; set; } public int 數值 { get; set; } }
共類 文字 = new 共類(); 文字.資料 = 文字控制元件.Text;
Thread 計算 = new Thread(new ParameterizedThreadStart(計算回車數量)); 計算.Start(文字); 計算.Abort();
-----------------------------------------------------------------------------------------------------
string 文字 = 文字控制元件.Text; new Thread(delegate() { 計算回車數量(文字); }).Start();
Thread 執行緒 = new Thread(delegate() { 執行緒另存檔案(); });
執行緒.SetApartmentState(System.Threading.ApartmentState.STA);//.MST
執行緒.Start();
------------------------------------------------------------------------------------
初涉的人為如何返回執行緒結果苦惱,採用很多種方法,我這裡採用委託直接返回執行緒結果;
下面看看委託:一般我是這樣寫委託就可以了,this.BeginInvoke(new 數字委託(計算字數), 回車);
/*還看到下面的一種寫法是在委託結束後回撥結果的:
//此處開始非同步執行,並且可以給出一個回撥函式
計算.BeginInvoke(文字控制元件.Text, new AsyncCallback(委託回撥), null);
delegate int 申明委託簽名(string 傳入值);
申明委託簽名 計算 = new 申明委託簽名(委託執行);//把委託和具體的方法關聯起來
public static int 委託執行(string 文字)//委託呼叫的方法
{
int 回車 = 0;
for (int i = 0, 數量 = 資料.ToString().Length; i < 數量; i++)
{
if (資料.ToString().Contains("\n")) 回車++;
if (資料.ToString().IndexOf("\n") + 1 < 資料.ToString().Length)
資料 = 資料.ToString().Substring(資料.ToString().IndexOf("\n") + 1);
else break;
if (!資料.ToString().Contains("\n")) break;
} return 回車;
}
public void 委託回撥(IAsyncResult 返回值)
{
this.BeginInvoke(new 數字委託(計算字數), 計算.EndInvoke(返回值));
}
*/
同樣我採用委託返回結果,如果直接用:計算字數(計算.EndInvoke(返回值));將提示錯誤。
使用 AsyncCallback 委託在一個單獨的執行緒中處理非同步操作的結果。AsyncCallback 委託表示在非同步操作完成時呼叫的回撥方法。回撥方法採用 IAsyncResult 引數,該引數隨後可用來獲取非同步操作的結果。
委託注意事項:委託的方法傳入引數必須對應,否則發生錯誤;
如:委託方法的傳入引數是string則宣告也必須是:private delegate void 文字委託(string 內容);
委託引數的數量必須與委託方法引數數量相等且型別必須一致;
如:委託方法的傳入引數是:DateTime 日期, DateTime 預測日期, string 內容,則宣告也必須對應:
private delegate void 委託(DateTime 日期, DateTime 預測日期, string 內容);
這裡順便提及是因為看到有些提問是否可以帶幾個引數;還有執行緒如何傳參的,有人回覆設一個公共變數來傳參,提問人覺得很遺憾,各人方法不盡相同,也屬正常,無可非議。
同時還應該注意:執行緒與非同步委託完成時間是不定的,設計時也應該慎重考慮或用除錯決定。
以上就是這些天專門玩執行緒與委託的一些經驗,今天憑著思路就寫了這些,知道寫得不好,看了莫笑。
初學靈活變通和試驗除錯相對比較弱,這裡再給一個直接呼叫多引數方法例子:
Thread 執行緒 = new Thread(delegate() { this.Invoke(new Action(() => 載入快捷選單(快捷選單, 快捷引數, 快捷事件))); });
執行緒.Start();
其實執行緒沒那麼難搞定,這裡給個定式:
Thread 執行緒 = new Thread(delegate()
{
this.Invoke(new Action(() => {/*如果涉及UI執行緒原始碼放這裡,如果沒有刪除這句*/}));
});
執行緒.Start();
new Thread(delegate() { this.Invoke(new Action(delegate() { 乾坤大挪移(快捷選單, 乾坤大挪移引數); })); }).Start();
使用匿名委託:
this.Invoke(new Action(delegate() { /*任何語句或方法*/}));
new Thread(delegate() { /*不涉及UI執行緒任何語句或方法*/ }).Start();
Thread 執行緒 = new Thread(delegate()
{/*原始碼放這裡就可以了*/}); 執行緒.Start();執行緒.Join();/*後續其他程式碼*/
有時方法外使用執行緒可以改為方法內使用執行緒(多引數傳遞)是一樣的,下面是改動的例子:
private void 乾坤大挪移(ContextMenuStrip 選單名, string[] 子引數)
{
Thread 執行緒 = new Thread(delegate()
{
this.Invoke(new Action(() =>
{
/*原始碼放這裡就可以了*/
}));
}); 執行緒.Start();
}
其實執行緒和委託使用起來是很方便的,特別是跨執行緒訪問也很簡單,只要是涉及到控制元件執行緒就使用委託就可以了,下面是改動的例子:
private void 時間_Tick(object sender, EventArgs e)
{
Thread 執行緒 = new Thread(delegate()
{
if (秒 < 59) 秒++; else { 秒 = 0; 分++; } if (分 == 60) { 分 = 0; 時++; } if (時 == 5) 時 = 0;
this.Invoke(new Action(() =>
顯示時間.Text = DateTime.Parse(時.ToString("0:") + 分.ToString("00:") + 秒.ToString("00")).ToLongTimeString()));
}); 執行緒.Start();
}
當使用非同步委託(BeginInvoke)需注意,有可能造成介面反應更忙,一般不與介面反應有關不輕易使用。
private void button1_Click(object sender, EventArgs e)
{
IAsyncResult 呼叫返回 = listBox1.BeginInvoke(new Action(() =>
{
Thread.Sleep(2000);
listBox1.Items.Add(Thread.CurrentThread.Name);
}));
listBox1.EndInvoke(呼叫返回);
listBox1.Invoke((EventHandler)delegate
{
Thread.Sleep(2000);
listBox1.Items.Add(Thread.CurrentThread.Name);//執行到這裡,其實把上一個函式的睡著的非同步給弄醒了
});
listBox1.BeginInvoke(new MethodInvoker(delegate
{
Thread.Sleep(2000);//呼叫的時候需要等待的時間,非同步是指CPU自己有空閒的時候合理分配空間給予執行;跟此時間無關;
listBox1.Items.Add(Thread.CurrentThread.Name);
}));
}補充參考
var t = Task.Factory.StartNew(() => button1_Click(null ,null ));
System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(一些長期任務));
System.Threading.ThreadPool.QueueUserWorkItem(
new System.Threading.WaitCallback(另一個長期任務),傳遞);
private void 一些長期任務(Object state)
{
// 插入程式碼來執行一項艱鉅的任務。
this.Invoke(new Action(() => { resultLabel.Text = "0"; }));
int aa = 100;
do
{
System.Threading.Thread.Sleep(1000);
this.Invoke(new Action(() => { resultLabel.Text = (int.Parse(resultLabel.Text) + 1).ToString(); }));
} while (--aa > 0);
}
private void 另一個長期任務(Object 引數)
{
// 插入程式碼來執行一項艱鉅的任務,引數包裝。
string aa=引數.ToString();
}
-----------------------------------------------------------------------------------
有關封裝與三目運算子應用:
Func<
string
,
bool
> 邏輯 =
delegate
(
string
年資訊)
{
if
(年資訊.Contains(
"11"
))
return
false
;
return
true
;
/*在這裡可以寫多語句處理,寫在呼叫之前*/
};
Func<string, string[], bool> 查對 = (string 資料, string[] 隔符) =>
{
return 隔符.Select(元素 => 資料.Contains(元素)).Any(比 => 比 == true);
};
return
(年資訊.Contains(
"00"
)) ?
true
: 邏輯(年資訊);
上面利用有參有返回(僅1個引數和返回值)
委託有引數無返回:
Action<DateTime[]> 日期計算 = delegate(DateTime[] 日期)
{
/*在這寫處理程式碼*/
};
呼叫:日期計算(new DateTime[] { 日期1, 日期2 });
委託無參有返回:
Func<string[]> 處理 = delegate
{
string[] 內容 = new string[0];
/*在這寫處理程式碼*/
return 內容;
};
Action 顯示 =
delegate
()
{
升起提示窗體(顯示事件.Text.Replace(
"\r\n"
,
""
));
};
Action 顯示 = ()=>
{
};
Action<int> 顯示 = (引數)=>
{
};
顯示(/*在需要呼叫的地方寫這行程式碼*/);
Parallel.Invoke(new System.Action(delegate() {/*包含不適合於"雷姆達表示式的"程式碼塊*/}));
Parallel.Invoke(delegate()
{
this.BeginInvoke(new Action(delegate()
{ /*適用巢狀迴圈提高速度*/}));
});
下面轉來自微軟程式碼例子:
Action<object> action = (object obj) =>
{
Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
};
Task t1 = new Task(action, "alpha");
Task t2 = Task.Factory.StartNew(action, "beta");
t2.Wait();
t1.Start();
Console.WriteLine("t1 has been launched. (Main Thread={0})", Thread.CurrentThread.ManagedThreadId);
t1.Wait();
Task t3 = new Task(action, "gamma");
t3.RunSynchronously();
t3.Wait();
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
}
private void startAsyncButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// 啟動非同步操作。
backgroundWorker1.RunWorkerAsync();
}
}
private void cancelAsyncButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
// 取消非同步操作。
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// 執行耗時的操作,並報告進度。
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = (e.ProgressPercentage.ToString() + "%");
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
resultLabel.Text = "取消!";
}
else if (e.Error != null)
{
resultLabel.Text = "錯誤: " + e.Error.Message;
}
else
{
resultLabel.Text = "做!";
}
}
Public Sub 定時事件(ByVal state As Object)
Me.BeginInvoke(
New Action(
Sub()
移動字幕.Left = 移動字幕.Left - 1
If 移動字幕.Right < 0 Then 移動字幕.Left = Me.Width
End Sub)
)
End Sub
void 控制元件非同步處理(Action 無返回方法)
{
Parallel.Invoke(delegate()
{
this.BeginInvoke(new Action(delegate()
{
無返回方法();
}));
});
}
void 背景(DataTable 資料表)
{
迴圈 = 資料表.Rows.Count - 1; int 列 = 資料表.Columns.Count - 1;
if (迴圈 > 0) while (true)
{
資料列表.Rows[迴圈].Cells[列].Style.BackColor = Color.Transparent;
if (--列 < 0) { if (--迴圈 < 0) break; 列 = 資料表.Columns.Count - 1; }
}
}
void 顯示(DataTable 資料表)
{
資料列表.DataSource = null;
資料列表.DataSource = 資料表;
資料列表.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
if (!new System.IO.FileInfo(@System.Environment.CurrentDirectory + "\\中草藥資料表.XML").Exists)
動態表格(new string[] { "名稱", "別名", "性味", "歸經", "主治" });
else
{
資料表載入(中草藥資料表);
控制元件非同步處理(() => 顯示(中草藥資料表));
控制元件非同步處理(() => 背景(中草藥資料表));
this.Text += " 已載入:" + 中草藥資料表.Rows.Count + "條資料。";
}
if (new System.IO.FileInfo(@System.Environment.CurrentDirectory + "\\中草藥樹表.XML").Exists)
{
DataTable 中草藥樹表 = new DataTable("中草藥樹表");
資料表載入(中草藥樹表);
控制元件非同步處理(() => 玄龍戲珠無級樹(中草藥樹表));
}