C#線程(異步委托)


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;
}

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM