什么是異步編程
什么是異步編程呢乃戈?舉個(gè)簡(jiǎn)單的例子:
using System.Net.Http;
using System.Threading.Tasks;
using static System.Console;
namespace Core
{
class Async
{
static void Main()
{
Start();
End();
}
static void Wait()=>WriteLine("waiting...");
static void End()=>WriteLine("end...");
static int Start()
{
WriteLine("start...");
HttpClient client = new HttpClient();
Waiting();
var result = client.GetStringAsync("https://www.visualstudio.com/");
string str = result.Result;
return str.Length;
}
}
}
上面這段代碼中,Main方法中的代碼是按照自上而下的順序執(zhí)行的。網(wǎng)絡(luò)狀況不佳時(shí)策泣,Start()
方法是比較耗時(shí)(注意,這里在Start
方法中調(diào)用了異步方法GetStringAsync
抬吟,但該方法在此處是以同步方式執(zhí)行的萨咕,具體原因下文會(huì)進(jìn)行說(shuō)明),在Start()
方法執(zhí)行完畢之前火本,整個(gè)程序處于阻塞狀態(tài)任洞。
而異步編程可以很好的解決這個(gè)問(wèn)題,一句簡(jiǎn)單的話來(lái)概括異步編程就是发侵,程序無(wú)須按照代碼順序自上而下的執(zhí)行交掏。
async/await
C#5.0新增了async和await關(guān)鍵字,使用這兩個(gè)關(guān)鍵字可以大大簡(jiǎn)化異步編程
使用 async 關(guān)鍵字可將方法刃鳄、lambda 表達(dá)式或匿名方法標(biāo)記為異步盅弛,即,方法中應(yīng)該包含一個(gè)或多個(gè)await表達(dá)式,但async關(guān)鍵字本身不會(huì)創(chuàng)建異步操作挪鹏。
public async Task Asy()
{
}
這里需要注意一點(diǎn)见秽,若使用async關(guān)鍵字標(biāo)記的方法中沒(méi)有使用await關(guān)鍵字(編譯器會(huì)給出警告但不報(bào)錯(cuò)),那么該方法將會(huì)以同步方式執(zhí)行讨盒。
定義異步方法的幾點(diǎn)要求
定義一個(gè)異步方法應(yīng)滿足以下幾點(diǎn):
- 使用async關(guān)鍵字來(lái)修飾方法
- 在異步方法中使用await關(guān)鍵字(不使用編譯器會(huì)給出警告但不報(bào)錯(cuò))解取,否則異步方法會(huì)以同步方式執(zhí)行
- 盡量不使用void作為返回類型,若希望異步方法返回void類型返顺,請(qǐng)使用Task
- 異步方法名稱以Async結(jié)尾
- 異步方法中不能聲明使用ref或out關(guān)鍵字修飾的變量
下面定義一個(gè)異步方法StartAsync()
:
static async Task<int> StartAsync()
{
HttpClient client = new HttpClient();
var str = await client.GetStringAsync("https://www.visualstudio.com/");
return str.Length;
}
異步方法的返回類型
- Task<T>
如果在調(diào)用匿名方法時(shí)使用了await關(guān)鍵字禀苦,且匿名方法的返回類型是Task<T>,那么我們得到的返回類型是T遂鹊。若未使用await關(guān)鍵字振乏,則返回類型是Task。
未使用await秉扑,調(diào)用GetStringAsync方法時(shí)result是Task類型
從上圖我們可以看到調(diào)用GetStringAsync方法時(shí)未使用await關(guān)鍵字慧邮,result是Task類型,我們可以通過(guò)GetType()方法來(lái)獲取result的詳細(xì)類型信息:
從上圖可以看到result的類型全名是System.Threading.Tasks.Task
從上圖我們可以看到使用await關(guān)鍵字時(shí)误澳,result是string類型,而匿名方法GetStringAsync的返回類型是Task<string>
Task
如果在調(diào)用匿名方法時(shí)使用了await關(guān)鍵字,且匿名方法的返回類型是Task,那么我們得到的返回類型是void璃饱。若為使用await關(guān)鍵字脊凰,則得到的返回類型是Task。void
不建議使用void作為異步方法的返回值。
因?yàn)槭褂肨ask或Task<TResult>任務(wù)作為返回值,其屬性攜帶有關(guān)其狀態(tài)和歷史記錄的信息,如任務(wù)是否完成毡琉、異步方法是否導(dǎo)致異常或已取消以及最終結(jié)果是什么妙色。而await運(yùn)算符可訪問(wèn)這些屬性桅滋。
異步方法執(zhí)行流程
上圖是微軟官方提供的講解異步程序執(zhí)行流程的圖示,并附有解釋說(shuō)明:
The numbers in the diagram correspond to the following steps.
- An event handler calls and awaits the AccessTheWebAsync async method.
- AccessTheWebAsync creates an HttpClient instance and calls the GetStringAsync asynchronous method to download the contents of a website as a string.
- Something happens in GetStringAsync that suspends its progress. Perhaps it must wait for a website to download or some other blocking activity. To avoid blocking resources, GetStringAsync yields control to its caller, AccessTheWebAsync.
GetStringAsync returns a Task<TResult> where **TResult **is a string, and AccessTheWebAsync assigns the task to thegetStringTask variable. The task represents the ongoing process for the call to GetStringAsync, with a commitment to produce an actual string value when the work is complete. - Because getStringTask hasn't been awaited yet, AccessTheWebAsync can continue with other work that doesn't depend on the final result from GetStringAsync. That work is represented by a call to the synchronous method DoIndependentWork.
- DoIndependentWork is a synchronous method that does its work and returns to its caller.
- AccessTheWebAsync has run out of work that it can do without a result from getStringTask. AccessTheWebAsync next wants to calculate and return the length of the downloaded string, but the method can't calculate that value until the method has the string.
Therefore, AccessTheWebAsync uses an await operator to suspend its progress and to yield control to the method that called AccessTheWebAsync. AccessTheWebAsync returns a Task<int> to the caller. The task represents a promise to produce an integer result that's the length of the downloaded string.
Note
If GetStringAsync (and therefore getStringTask) is complete before AccessTheWebAsync awaits it, control remains inAccessTheWebAsync. The expense of suspending and then returning to AccessTheWebAsync would be wasted if the called asynchronous process (getStringTask) has already completed and AccessTheWebSync doesn't have to wait for the final result.
Inside the caller (the event handler in this example), the processing pattern continues. The caller might do other work that doesn't depend on the result from AccessTheWebAsync before awaiting that result, or the caller might await immediately. The event handler is waiting for AccessTheWebAsync, and AccessTheWebAsync is waiting for GetStringAsync.
- GetStringAsync completes and produces a string result. The string result isn't returned by the call to GetStringAsync in the way that you might expect. (Remember that the method already returned a task in step Instead, the string result is stored in the task that represents the completion of the method, getStringTask. The await operator retrieves the result from getStringTask. The assignment statement assigns the retrieved result to urlContents.
- When AccessTheWebAsync has the string result, the method can calculate the length of the string. Then the work ofAccessTheWebAsync is also complete, and the waiting event handler can resume. In the full example at the end of the topic, you can confirm that the event handler retrieves and prints the value of the length result.
If you are new to asynchronous programming, take a minute to consider the difference between synchronous and asynchronous behavior. A synchronous method returns when its work is complete (step 5), but an async method returns a task value when its work is suspended (steps 3 and 6). When the async method eventually completes its work, the task is marked as completed and the result, if any, is stored in the task.
解釋雖是英文身辨,但并沒(méi)有太難的單詞丐谋,是可以看懂其意思的。通過(guò)上面的說(shuō)明煌珊,我們可以知道:
在遇到awiat關(guān)鍵字之前号俐,程序是按照代碼順序自上而下以同步方式執(zhí)行的。
在遇到await關(guān)鍵字之后定庵,系統(tǒng)做了以下工作:
- 異步方法將被掛起
- 將控制權(quán)返回給調(diào)用者
- 使用線程池中的線程(而非額外創(chuàng)建新的線程)來(lái)計(jì)算await表達(dá)式的結(jié)果吏饿,所以await不會(huì)造成程序的阻塞
- 完成對(duì)await表達(dá)式的計(jì)算之后踪危,若await表達(dá)式后面還有代碼則由執(zhí)行await表達(dá)式的線程(不是調(diào)用方所在的線程)繼續(xù)執(zhí)行這些代碼
使用一段代碼來(lái)進(jìn)行驗(yàn)證:
static void Main()
{
Task<int> task = StartAsync();
Thread.Sleep(5000);
End();
}
static async Task<int> StartAsync()
{
WriteLine("start...");
HttpClient client = new HttpClient();
var result = client.GetStringAsync("https://www.visualstudio.com/");
string str = await result;
return str.Length;
}
執(zhí)行代碼
從上圖左側(cè)的調(diào)用棧中可以看到,在遇到await關(guān)鍵字之前猪落,異步方法StartAsync
自上而下同步執(zhí)行贞远。注意,這里異步方法GetStringAsync
方法是被掛起的笨忌,不會(huì)造成程序的阻塞蓝仲,控制權(quán)回到調(diào)用者StartAsync
中,仔細(xì)看英文解釋中的第3步官疲。
然后在Debug Console中輸入System.Threading.Thread.Current
查看當(dāng)前工作線程信息袱结,以及System.Threading.Thread.CurrentThread.IsThreadPoolThread
查看當(dāng)前線程是否在線程池中。
從上圖我們看到袁余,當(dāng)前線程Id是1擎勘,不在線程池中咱揍。繼續(xù)執(zhí)行程序:
遇到await關(guān)鍵字后颖榜,異步方法StartAsync
被掛起,控制權(quán)也回到了調(diào)用者M(jìn)ain方法中煤裙。
從上圖我們可以看到異步方法
StartAsync
中的result變量的Status屬性值是WaitingForActivation
掩完,Result屬性值是Not yet computed
。
代碼繼續(xù)執(zhí)行硼砰,將Main方法所在線程接掛起5秒且蓬,系統(tǒng)使用線程池中的線程計(jì)算await表達(dá)式的值:
從上圖我們可以看到,程序已經(jīng)成功計(jì)算出await表達(dá)式的值题翰,變量result的Status屬性值變成了RanToCompletion
恶阴。完成對(duì)await表達(dá)式的計(jì)算之后,程序繼續(xù)執(zhí)行后面的代碼(return str.Length
)豹障。
再看此時(shí)的工作線程信息:
我們看到冯事,當(dāng)前線程Id是5且存在于線程池中。
從這里我們可以得知異步是借助于多線程來(lái)實(shí)現(xiàn)的血公。
Task
Task類擁有執(zhí)行異步方法的兩個(gè)方法:Task.Run()
,Task.Run<T>
昵仅,Task.Run
以及Task.Run<T>
使用線程池中的線程來(lái)執(zhí)行代碼,它和使用await關(guān)鍵字的區(qū)別是:Task.Run直接使用線程池中的線程累魔,而使用await的異步方法是在遇到await關(guān)鍵字后才使用多線程摔笤。
Thread
線程是前面所說(shuō)的異步(async/await)和任務(wù)(Task)的基礎(chǔ)。和線程緊密相關(guān)的另外一個(gè)概念是進(jìn)程垦写,這里不多贅述吕世。
ThreadPool
線程也是對(duì)象,頻繁的創(chuàng)建和銷毀線程比較影響性能梯投,.NET提供線程池使得我們能夠復(fù)用線程對(duì)象從而避免頻繁創(chuàng)建和銷毀線程命辖。
結(jié)語(yǔ)
自己創(chuàng)建線程比較麻煩但能夠更好的控制程序的運(yùn)行渴析,使用async/await關(guān)鍵字來(lái)編碼顯得較為簡(jiǎn)潔,但對(duì)程序的控制力度會(huì)有所降低吮龄。
參考文章:
Asynchronous Programming with async and await (C#)
async
await
走進(jìn)異步編程的世界 - 開始接觸 async/await
C#執(zhí)行異步操作的幾種方式比較和總結(jié)
thread task parallel plinq async await多線程 任務(wù)及異步編程
走進(jìn)異步編程的世界 - 在 GUI 中執(zhí)行異步操作
Async/Await - Best Practices in Asynchronous Programming