C#線程(異步委託)

https://www.cnblogs.com/tlmbem/p/10832853.html

delegate匿名方法(匿名函數)

1. 函數和方法等價。匿名方法能夠讓你聲明一個方法體而不需要給它指定一個名字,它們以一個“普通的”方法存在,但是在你的代碼中沒有任何方法顯式調用它。,返回參數不需要聲明,會根據[語句塊]決定。

2. 匿名方法只能在使用委託的時候創建,它們通過delegate關鍵字創建或者Lambda表達式(匿名函數)。定義方式:delegate(顯式參數,顯式參數) {語句塊}

 delegate(bool x) { return x ? 1 : 2; }

3. 匿名函數總是和委託一齊使用 :    定義委託變量=匿名函數

 Func<bool, int> fun = delegate(bool x) { return x ? 1 : 2; };

 

Lambda表達式(匿名函數)

1. "Lambda表達式"可以是一個表達式也可以是一個匿名函數,Lambda表達式都使用Lambda運算符“=>”。Lambda運算符的左邊是[輸入參數(如果有)],右邊是[表達式]或[語句塊],返回參數不需要聲明,會根據[表達式]或[語句塊]決定。表達式只能有一條語句,語句塊可以有多條語句。

(參數1,參數2)=>表達式或語句塊 

1. () => { return 1; };          //無參數 =>語句塊 
2. ( x) => x * 5;                //單參數,隱式類型 =>表達式 
3. ( x) => { return x * 5; };    //單參數,隱式類型=>語句塊 
4. (int x) => x * 5;             //單參數,顯式類型=>表達式 
5. (int x) => { return x * 5; }; //單參數,顯式類型=>語句塊 
6. ( x, y) => x * y ;            //多參數,隱式類型=> 表達式

 2. Lambda表達式可以和委託一齊使用 :    定義委託變量=Lambda表達式

 Func<bool, int> fun = (bool x) => { return x ? 1 : 2; };

 

delegate委託

1. 委託的用途就是把方法當作參數來進行傳遞。

public delegate int MethodtDelegate(int x, int y);//定義委託
MethodtDelegate md = (x, y) => { return x * y; };//定義委託變量
md(1, 2);//調用委託

2. 可以用+=對一個委託變量綁定多個方法。這叫多播委託,利用 -=移除方法。

md += (x, y) => { return x + y; };
md += (x, y) => { return x - y;  };

3. C#預定義的三種泛型委託Action<>、Func<>、Predicate<>

3.1. Action<>  至少0個參數,至多16個參數,無返回值。

Action<intstringfloat> action=null;

3.2. Func<>    至少1個參數(返回值參數),至多16個參數,有返回值。最後一個參數爲返回值參數

Func<intstringfloat> func=null;

3.3. Predicate<> 只有一個參數,默認返回bool。

Predicate一般用於Lambda表達式裏面的條件,表示定義一組條件並確定指定對象是否符合這些條件的方法,此方法常在集合的查找中被用到。如:數組,正則拼配的結果集中被用到。

Predicate<int> predicate=null;

 

0 異步委託介紹:

Invoke 開始同步調用委託(同步調用Invoke,無論是否新開線程都會導致阻塞Invoke所在的線程)
BeginInvoke 開始異步調用委託
EndInvoke 返回異步調用的結果集(當使用BeginInvoke異步調用方法時,如果方法未執行完,EndInvoke方法就會一直阻塞,直到被調用的方法執行完畢)
AsyncCallback 結束異步調用後要執行的操作。

1. (Control的Invoke和BeginInvoke)是在Control線程上調用。所以如果在Control線程上調用Control的Invoke或BeginInvoke來進行調用某個委託方法來達到跨線程或異步是錯誤的。那Control的Invoke和BeginInvoke的用途是什麼呢,他的用途是讓其他線程的某個方法在Control所在線程上執行。如果在其他線程上調用Control的Invoke,這時候其他線程會被阻塞,直到Control線程執行完其他線程纔會在繼續執行。而Control的BeginInvoke不會讓其他線程阻塞。

2. (Delegate的Invoke和BeginInvoke)是在線程池中調用。 所以一般如果某個方法執行時間特別長,都會用Delegate的BeginInvoke執行,這樣Control線程就不會被阻塞。但要注意,如果在Control線程中調用Delegate的Invoke,雖然Delegate的Invoke是從線程池的線程同步調用,但他還是會阻塞Control線程的,所以要用Delegate的BeginInvoke才能實現異步。還有一種情況就是在線程池上用Delegate的Invoke會導致線程池上Delegate所在線程被阻塞,直到Control所在線程上執行完線程池上Delegate所在線程才能繼續執行。

3. 使用BeginInvoke時特別要注意的就是BeginInvoke裏面所用到的變量必須全部使用參數形式傳進去,防止異步時被幕改變量值。

1 異步有阻塞:

例如有一個程序現在要分別調用 接口1和接口2,並要獲取到他們兩個接口的結果集後才能繼續執行後面的代碼。接口1調用耗時爲10秒,接口2調用耗時5秒,如果是同步調用這兩個接口並要處理這兩個接口返回值時,一共需要耗時15秒。但如果我們使用異步同時調用這兩個接口,那我們一般最多隻需要10秒就可以返回兩個接口的結果集。但是由於我們要在調用接口的這個方法退出前必須要獲取到接口返回的結果,所以我們要用EndInvoke;這時候調用線程會處於阻塞狀態。

複製代碼
//定義調用接口1委託
Func<string, string> getInfo1 = (string txt) => { /*調用接口1*/ Thread.Sleep(10000); return "1"; };
//定義調用接口2委託
Func<string, string> getInfo2 = (string txt) => { /*調用接口2*/ Thread.Sleep(5000); return "2"; };

//開始調用接口1
IAsyncResult ar1 = getInfo1.BeginInvoke(調用接口1, null, null);
//開始調用接口2
IAsyncResult ar2 = getInfo2.BeginInvoke(調用接口2, null, null);

string result1 = getInfo1.EndInvoke(ar1);//處理接口1返回結果
string result2 = getInfo2.EndInvoke(ar2);//處理接口2返回結果

for (int i = 0; i < 10; i++)
{

}

複製代碼

 

2 異步有阻塞加超時:

利用WaitOne方法,這個方法的超時設置指代碼運行但這句代碼時開始計算時間,所以同時有多個BeginInvoke異步調用時要注意。

複製代碼
//定義調用接口1委託
Func<string, string> getInfo1 = (string txt) => { /*調用接口1*/ Thread.Sleep(1000); return "1"; };
//定義調用接口2委託
Func<string, string> getInfo2 = (string txt) => { /*調用接口2*/ Thread.Sleep(7000); return "2"; };

//開始調用接口1
IAsyncResult ar1 = getInfo1.BeginInvoke(調用接口1, null, null);
//開始調用接口2
IAsyncResult ar2 = getInfo2.BeginInvoke(調用接口2, null, null);

//string result1 = getInfo1.EndInvoke(ar1);//處理接口1返回結果
//string result2 = getInfo2.EndInvoke(ar2);//處理接口2返回結果

int TimeOut = 5000;//超時時間爲5秒
Stopwatch sw = new Stopwatch();
sw.Start();

if (ar1.AsyncWaitHandle.WaitOne(TimeOut))
{
getInfo1.EndInvoke(ar1);
//方法成功執行返回值
sw.Stop();
TimeOut
-= (int)sw.ElapsedMilliseconds;//減掉第一個異步的耗時
}
else
{
sw.Stop();
TimeOut
= 0;
//超時
}

if (ar2.AsyncWaitHandle.WaitOne(TimeOut))
{
getInfo2.EndInvoke(ar2);
//方法成功執行返回值
}
else
{
//超時
}

for (int i = 0; i < 10; i++)
{

}

複製代碼

 

3 異步無阻塞單向:

例如異步寫日誌,Control線程把要寫入的日誌信息傳遞給另外一個線程,不管另外一個線程的執行進度,Control線程繼續執行其他工作。

//定義寫日誌委託
Action<string> writeLog = (string log) => { /*寫日誌內容*/ };
//在寫日誌委託回調函數李自動調用EndInvoke
AsyncCallback callFun = (result) => { ((Action<string>)result.AsyncState).EndInvoke(result); };
//開始異步寫日誌
writeLog.BeginInvoke("日誌內容", callFun, writeLog);

 

4 異步無阻塞加回調:

例如有一個UI程序按鈕現在要分別調用接口1和接口2,並要獲取到他們兩個接口的結果集後要更新按鈕上的文字。接口1調用耗時爲10秒,接口2調用耗時5秒,如果是同步調用這兩個接口並要處理這兩個接口返回值時,一共需要耗時15秒。UI畫面會卡死15秒。但如果我們使用上面1的方法,那UI界面也至少要卡死10秒。所以如果我們把EndInvoke放在一個AsyncCallback裏面處理,當我們按下按鈕時,會異步調用這兩個接口,由於UI線程沒有被阻塞,所以界面不會卡死,當等到接口被調用完成後將會自動調用AsyncCallback利用EndInvoke返回結果處理按鈕上的文字。

複製代碼
private void asynbtn_Click(object sender, EventArgs e)
{
//定義調用接口1委託
Func<string, string> getInterface1 = (string txt) => { Thread.Sleep(5000); return "1"; };
//定義調用接口2委託
Func<string, string> getInterface2 = (string txt) => { Thread.Sleep(7000); return "2"; };

//異步全局信息
AsyncGlobalInfo agi = new AsyncGlobalInfo();
agi.gi1
= getInterface1;
agi.gi2
= getInterface2;

//開始調用接口1
IAsyncResult ar1 = getInterface1.BeginInvoke(調用接口1參數, new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result1 = ic.gi1.EndInvoke(result); ic.isCompleted1 = true; setText(ic); }), agi);
//開始調用接口2
IAsyncResult ar2 = getInterface2.BeginInvoke(調用接口2參數, new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result2 = ic.gi2.EndInvoke(result); ic.isCompleted2 = true; setText(ic); }), agi);
}
//定義處理按鈕委託
public void setText(AsyncGlobalInfo infoall)
{
//判斷所有的異步操作都完成
if (infoall.isCompleted1 && infoall.isCompleted2)
{
this.Invoke(new Action(() => { this.button1.Text = infoall.result1 + " " + infoall.result2; }));
}
}

/// <summary>
/// 異步全局信息
/// </summary>
public class AsyncGlobalInfo
{
public Func<string, string> gi1;
public bool isCompleted1 = false;
public string result1 = null;
public Func<string, string> gi2;
public bool isCompleted2 = false;
public string result2 = null;
}

複製代碼

 

5 異步無阻塞加回調加憑據:

以上4的方法都有一個問題,那就是耗時問題。1如果這個異步調用必須在指定時間內完成,但4的方法都沒有時間限制,所以有可能造成永久等待。2例如一個按鈕點擊了兩次,如果第一次處理時間比第二次長,有可能第二次結果會被第一次覆蓋。那要怎麼做呢。一般添加一個憑據來驗證異步調用和回調是同一次。

複製代碼
private string textBoxToken = "";//textBox憑據

private void asynbtn_Click(object sender, EventArgs e)
{
//定義調用接口1委託
Func<string, string> getInterface1 = (string txt) => { Thread.Sleep(5000); return 1; };
//定義調用接口2委託
Func<string, string> getInterface2 = (string txt) => { Thread.Sleep(5000); return 2; };

textBoxToken = asynbtn;
//異步全局信息
AsyncGlobalInfo agi = new AsyncGlobalInfo();
agi.gi1
= getInterface1;
agi.gi2
= getInterface2;
agi.token
= textBoxToken;

//開始調用接口1
IAsyncResult ar1 = getInterface1.BeginInvoke(調用接口1參數, new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result1 = ic.gi1.EndInvoke(result); ic.isCompleted1 = true; if (this.textBoxToken == ic.token) setText(ic); }), agi);
//開始調用接口2
IAsyncResult ar2 = getInterface2.BeginInvoke(調用接口2參數, new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result2 = ic.gi2.EndInvoke(result); ic.isCompleted2 = true; if (this.textBoxToken == ic.token) setText(ic); }), agi);
}

private void copybtn_Click(object sender, EventArgs e)
{
//定義調用接口1委託
Func<string, string> getInterface1 = (string txt) => { Thread.Sleep(3000); return 3; };
//定義調用接口2委託
Func<string, string> getInterface2 = (string txt) => { Thread.Sleep(3000); return 4; };

textBoxToken = copybtn;
//異步全局信息
AsyncGlobalInfo agi = new AsyncGlobalInfo();
agi.gi1
= getInterface1;
agi.gi2
= getInterface2;
agi.token
= textBoxToken;

//開始調用接口1
IAsyncResult ar1 = getInterface1.BeginInvoke(調用接口1參數, new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result1 = ic.gi1.EndInvoke(result); ic.isCompleted1 = true; if (this.textBoxToken == ic.token) setText(ic); }), agi);
//開始調用接口2
IAsyncResult ar2 = getInterface2.BeginInvoke(調用接口2參數, new AsyncCallback((result) => { AsyncGlobalInfo ic = (AsyncGlobalInfo)result.AsyncState; ic.result2 = ic.gi2.EndInvoke(result); ic.isCompleted2 = true; if (this.textBoxToken == ic.token) setText(ic); }), agi);
}

//定義處理按鈕委託
public void setText(AsyncGlobalInfo infoall)
{
//判斷所有的異步操作都完成
if (infoall.isCompleted1 && infoall.isCompleted2)
{
this.Invoke(new Action(() => { this.textBox1.Text = infoall.result1 + " " + infoall.result2; }));
}
}

/// <summary>
/// 異步全局信息
/// </summary>
public class AsyncGlobalInfo
{
public string token;
public Func<string, string> gi1;
public bool isCompleted1 = false;
public string result1 = null;
public Func<string, string> gi2;
public bool isCompleted2 = false;
public string result2 = null;
}

複製代碼

上面爲什麼阻塞的用來超時處理,無阻塞用憑據處理呢,你可以理解阻塞的是按代碼順序執行的,無阻塞類似於併發。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章