C#異步編程

什么是異步編程

什么是異步編程呢乃戈?舉個(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ì)類型信息:


匿名方法返回類型Task<T>

從上圖可以看到result的類型全名是System.Threading.Tasks.Task

使用await關(guān)鍵字舟陆,調(diào)用GetStringAsync()方法時(shí)result是string類型

從上圖我們可以看到使用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í)行流程

上圖是微軟官方提供的講解異步程序執(zhí)行流程的圖示,并附有解釋說(shuō)明:

The numbers in the diagram correspond to the following steps.

  1. An event handler calls and awaits the AccessTheWebAsync async method.
  2. AccessTheWebAsync creates an HttpClient instance and calls the GetStringAsync asynchronous method to download the contents of a website as a string.
  3. 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.
  4. 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.
  5. DoIndependentWork is a synchronous method that does its work and returns to its caller.
  6. 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.

  1. 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.
  2. 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)做了以下工作:

  1. 異步方法將被掛起
  2. 將控制權(quán)返回給調(diào)用者
  3. 使用線程池中的線程(而非額外創(chuàng)建新的線程)來(lái)計(jì)算await表達(dá)式的結(jié)果吏饿,所以await不會(huì)造成程序的阻塞
  4. 完成對(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í)行代碼


await之前

從上圖左側(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

遇到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ì)算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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俭茧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子漓帚,更是在濱河造成了極大的恐慌母债,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尝抖,死亡現(xiàn)場(chǎng)離奇詭異毡们,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)昧辽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門衙熔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人搅荞,你說(shuō)我怎么就攤上這事红氯。” “怎么了咕痛?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵痢甘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我茉贡,道長(zhǎng)塞栅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任腔丧,我火速辦了婚禮放椰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘愉粤。我一直安慰自己砾医,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布科汗。 她就那樣靜靜地躺著藻烤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪头滔。 梳的紋絲不亂的頭發(fā)上怖亭,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音坤检,去河邊找鬼兴猩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛早歇,可吹牛的內(nèi)容都是我干的倾芝。 我是一名探鬼主播讨勤,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晨另!你這毒婦竟也來(lái)了潭千?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤借尿,失蹤者是張志新(化名)和其女友劉穎刨晴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體路翻,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狈癞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茂契。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝶桶。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖掉冶,靈堂內(nèi)的尸體忽然破棺而出真竖,到底是詐尸還是另有隱情,我是刑警寧澤郭蕉,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布疼邀,位于F島的核電站喂江,受9級(jí)特大地震影響召锈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜获询,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一涨岁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吉嚣,春花似錦梢薪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至秋泄,卻和暖如春琐馆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恒序。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工瘦麸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人歧胁。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓滋饲,卻偏偏與公主長(zhǎng)得像厉碟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屠缭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容