線程(Thread)與委託(Invoke和BeginInvoke)
這幾天專門玩線程與委託,到處查找資料看,漸漸明白了用法、寫法和一些注意事項;
描述:
什麼是進程呢?當一個程序開始運行時,它就是一個進程,進程所指包括運行中的程序和程序所使用到的內存和系統資源。而一個進程又是由多個線程所組成的,線程是程序中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程序計數器等),但代碼區是共享的,即不同的線程可以執行同樣的函數。
Control.Invoke 方法 (Delegate) :在擁有此控件的基礎窗口句柄的線程上執行指定的委託。
Control.BeginInvoke 方法 (Delegate) :在創建控件的基礎句柄所在線程上異步執行指定委託。
Control的Invoke和BeginInvoke的參數爲delegate,委託的方法是在Control的線程上執行的,也就是我們平時所說的UI線程。
何時採用簡單歸納:
1、提高CPU的利用率,從而提高了程序的效率;
2、當程序運行會卡住軟件界面,爲了使人不用焦慮等待時,採取線程與委託來處理,從而使軟件界面運行流暢;
3、處理大量數據時間較長(顯示一個進度條給界面)或不需要馬上得到結果反饋給軟件界面時;
注意事項:線程是各自獨立進行管理的,一個線程不能包含另一個線程;即:用Thread創建的線程是一個線程,Control是一個線程,這是兩個獨立的線程;委託則專屬於Control線程;至少目前我是這樣理解的,因此,初涉這個領域時不小心就會犯線程相互交涉而發生錯誤;更具體查看微軟或網絡上相關資料就不贅述了。
參考網址:
http://hi.baidu.com/jok607/blog/item/393746e72f513125b8382002.html
http://www.cnblogs.com/mashang/archive/2009/08/01/1536730.html
舉例是最生動的說明,看下面例子:
要執行的操作是計算一個文本控件中有多少個字符,包含多少回車數量:
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(返回值));將提示錯誤。
原因參考網址:
http://technet.microsoft.com/zh-cn/library/system.asynccallback(zh-tw).aspx
使用 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
;
/*在這裏可以寫多語句處理,寫在調用之前*/
};
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> 顯示 = (參數)=>
{
};
顯示(/*在需要調用的地方寫這行代碼*/);
並行處理:using System.Threading.Tasks;/*並行運算*/ Parallel.Invoke(() =>{/*代碼塊*/});
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