日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > C# >内容正文

C#

【转】C#与C++的发展历程第一 - 由C#3.0起

發(fā)布時(shí)間:2023/12/10 C# 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】C#与C++的发展历程第一 - 由C#3.0起 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

C#5.0作為第五個(gè)C#的重要版本,將異步編程的易用度推向一個(gè)新的高峰。通過新增的async和await關(guān)鍵字,幾乎可以使用同編寫同步代碼一樣的方式來編寫異步代碼。

本文將重點(diǎn)介紹下新版C#的異步特性以及部分其他方面的改進(jìn)。同時(shí)也將介紹WinRT程序一些異步編程的內(nèi)容。

?

C# async/await異步編程

寫async異步編程這部分內(nèi)容之前看了好多文章,反復(fù)整理自己的思路,盡力保證文章的正確性。盡管如此仍然可能存在錯(cuò)誤,請(qǐng)廣大園友及時(shí)指出,感謝感謝。

異步編程不是一個(gè)新鮮的話題,最早期的C#版本也內(nèi)建對(duì)異步編程的支持,當(dāng)然在顏值上無法與目前基于TAP,使用async/await的異步編程相比。異步編程要解決的問題就是許多耗時(shí)的IO可能會(huì)阻塞線程導(dǎo)致CPU空轉(zhuǎn)降低效率,或者一個(gè)長(zhǎng)時(shí)間的后臺(tái)任務(wù)會(huì)阻塞用戶界面。通過將耗時(shí)任務(wù)異步執(zhí)行來使系統(tǒng)有更高的吞吐量,或保持界面的響應(yīng)能力。例如界面在加載一幅來自網(wǎng)絡(luò)的圖像時(shí),還可以及時(shí)響應(yīng)用戶進(jìn)行的其他操作。

按前文慣例先上一張圖通覽一下TAP模式下異步編程的方方面面,然后由異步編程的發(fā)展來討論一下TAP異步模式。

圖1

APM

C# .NET最早出現(xiàn)的異步編程模式被稱為APM(Asynchronous Programming Model)。這種模式主要由一對(duì)Begin/End開頭的組成。BeginXXX方法用于啟動(dòng)一個(gè)耗時(shí)操作(需要異步執(zhí)行的代碼段),相應(yīng)的調(diào)用EndXXX來結(jié)束BeginXXX方法開啟的異步操作。BeginXXX方法和EndXXX方法之間的信息通過一個(gè)IAsyncResult對(duì)象來傳遞。這個(gè)對(duì)象是BeginXXX方法的返回值。如果直接調(diào)用EndXXX方法,則將以阻塞的方式去等待異步操作完成。另一種更好的方法是在BeginXXX倒數(shù)第二個(gè)參數(shù)指定的回調(diào)函數(shù)中調(diào)用EndXXX方法,這個(gè)回調(diào)函數(shù)將在異步操作完成時(shí)被觸發(fā),回調(diào)函數(shù)的第二個(gè)參數(shù)即是EndXXX方法所需要的IAsyncResult對(duì)象。

.NET中一個(gè)典型的例子如System.Net命名空間中的HttpWebRequest類里的BeginGetResponse和EndGetResponse這對(duì)方法:

IAsyncResult?BeginGetResponse(AsyncCallback?callback,?object?state) WebResponse?EndGetResponse(IAsyncResult?asyncResult)

由方法聲明即可看出,它們符合前述的模式。

APM使用簡(jiǎn)單明了,雖然代碼量稍多,但也在合理范圍之內(nèi)。APM兩個(gè)最大的缺點(diǎn)是:1.不支持進(jìn)度報(bào)告 2.不能方便的“取消”。

?

EAP

在C#?.NET第二個(gè)版本中,增加了一種新的異步編程模型EAP(Event-based Asynchronous Pattern),EAP模式的異步代碼中,典型特征是一個(gè)Async結(jié)尾的方法和Completed結(jié)尾的事件。XXXCompleted事件將在異步處理完成時(shí)被觸發(fā),在事件的處理函數(shù)中可以操作異步方法的結(jié)果。往往在EAP代碼中還會(huì)存在名為CancelAsync的方法用來取消異步操作,以及一個(gè)ProgressChanged結(jié)尾的事件用來匯報(bào)操作進(jìn)度。通過這種方式支持取消和進(jìn)度匯報(bào)也是EAP比APM更有優(yōu)勢(shì)的地方。通過后文TAP的介紹,你會(huì)發(fā)現(xiàn)EAP中取消機(jī)制沒有可延續(xù)性,并且不是很通用。

.NET2.0中新增的BackgroundWorker可以看作EAP模式的一個(gè)例子。另一個(gè)使用EAP的例子是被HttpClient所取代的WebClient類(新代碼應(yīng)該使用HttpClient而不是WebClient)。WebClient類中通過DownloadStringAsync方法開啟一個(gè)異步任務(wù),并有DownloadStringCompleted事件供設(shè)置回調(diào)函數(shù),還能通過CancelAsync方法取消異步任務(wù)。

?

TAP & async/await

.NET4.0開始新增了一個(gè)名為TPL的庫(kù)主要負(fù)責(zé)異步和并行操作的處理目標(biāo)就是使異步和并發(fā)操作有個(gè)統(tǒng)一的操作界面TPL庫(kù)的核心是Task類,有了Task幾乎不用像之前版本的異步和并發(fā)那樣去和Thread等底層類打交道,作為使用者的我們只需要處理好Task,Task背后有一個(gè)名為的TaskScheduler的類來處理Task在Thread上的執(zhí)行。可以這樣說TaskScheduler和Task就是.NET4.0中異步和并發(fā)操作的基礎(chǔ),也是我們寫代碼時(shí)不二的選擇。

對(duì)于Task可以將其理解為一個(gè)包裝委托對(duì)象(通常就是Action或Func對(duì)象)并執(zhí)行的容器,從Task對(duì)象的創(chuàng)建就可以看出:

Action?action?=?()?=>?Console.WriteLine("Hello?World"); Task?task1?=?new?Task(action);Func<object,?string>?func?=?name?=>?"Hello?World"?+?name; Task<string>?task2?=?new?Task<string>(func,?"hystar"?,?CancellationToken.None,TaskCreationOptions.None?);//接收object參數(shù)真蛋疼,很不容易區(qū)分重載,把參數(shù)都寫上吧。

執(zhí)行這個(gè)Task對(duì)象需要手動(dòng)調(diào)用Start方法:

task1.Start();

這樣task對(duì)象將在默認(rèn)的TaskScheduler調(diào)度下去執(zhí)行,TaskScheduler使用線程池中的線程,至于是新建還是使用已有線程這個(gè)對(duì)用戶是完全透明的。還也可以通過重載函數(shù)的參數(shù)傳入自定義的TaskScheduler。

關(guān)于TaskScheduler的調(diào)度,推薦園子里這篇文章,前半部分介紹了一些線程執(zhí)行機(jī)制,很值得一度。

當(dāng)我們用new創(chuàng)建一個(gè)Task對(duì)象時(shí),創(chuàng)建的對(duì)象是Created狀態(tài),調(diào)用Start方法后將變?yōu)閃aitingToRun狀態(tài)。至于什么時(shí)候開始執(zhí)行(進(jìn)入Running狀態(tài),由TaskScheduler控制,)。Task的創(chuàng)建執(zhí)行還有一種“快捷方式”,即Run方法:

Task.Run(()?=>?Console.WriteLine("Hello?World")); var?txt?=?await?Task<string>.Run(()?=>?"Hello?World");

這種方式創(chuàng)建的Task會(huì)直接進(jìn)入WaitingToRun狀態(tài)。

Task的其他狀態(tài)還有RanToCompletion,Canceled以及Faulted。在到達(dá)RanToCompletion狀態(tài)時(shí)就可以獲得Task<T>類型任務(wù)的結(jié)果。如果Task在狀態(tài)為Canceled的情況下結(jié)束,會(huì)拋出 OperationCanceledException。如果以Faulted狀態(tài)結(jié)束,會(huì)拋出導(dǎo)致任務(wù)失敗的異常。

Task同時(shí)服務(wù)于并發(fā)編程和異步編程(在Jeffrey?Richter的CLR via C#中分別稱這兩種模式為計(jì)算限制的異步操作和IO限制的異步操作,仔細(xì)想想這稱呼也很貼切),這里主要討論下Task和異步編程的相關(guān)的機(jī)制。其中最關(guān)鍵的一點(diǎn)就是Task是一個(gè)awaitable對(duì)象,這是其可以用于異步編程的基礎(chǔ)。除了Task,還有很多類型也是awaitable的,如ConfigureAwait方法返回的ConfiguredTaskAwaitable、WinRT平臺(tái)中的IAsyncInfo(這個(gè)后文有詳細(xì)說明)等。要成為一個(gè)awaitable類型需要符合哪些條件呢?其實(shí)就一點(diǎn),其中有一個(gè)GetAwaiter()方法,該方法返回一個(gè)awaiter。那什么是awaiter對(duì)象呢?滿足如下3點(diǎn)條件即可:

  • 實(shí)現(xiàn)INotifyCompletion或ICriticalNotifyCompletion接口

  • 有bool類型的IsCompleted屬性

  • 有一個(gè)GetResult()來返回結(jié)果,或是返回void

awaitable和awaiter的關(guān)系正如IEnumerable和IEnumerator的關(guān)系一樣。推而廣之,下面要介紹的async/await的幕后實(shí)現(xiàn)方式和處理yield語法糖的實(shí)現(xiàn)方式差不多。

Task類型的GetAwaiter()返回的awaiter是TaskAwaiter類型。這個(gè)TaskAwaiter很簡(jiǎn)單基本上就是剛剛滿足上面介紹的awaiter的基本要求。類似于EAP,當(dāng)異步操作執(zhí)行完畢后,將通過OnCompleted參數(shù)設(shè)置的回調(diào)繼續(xù)向下執(zhí)行,并可以由GetResult獲取執(zhí)行結(jié)果。

?

簡(jiǎn)要了解過Task,再來看一下本節(jié)的重點(diǎn) - async異步方法。async/await模式的異步也出來很久了,相關(guān)文章一大片,這里介紹下重點(diǎn)介紹下一些不容易理解和值得重點(diǎn)關(guān)注的點(diǎn)。我相信我曾經(jīng)碰到的困惑也是很多人的遇到的困惑,寫出來和大家共同探討。

語法糖

對(duì)async/await有了解的朋友都知道這兩個(gè)關(guān)鍵字最終會(huì)被編譯為.NET中和異步相關(guān)的狀態(tài)機(jī)的代碼。這一部分來具體看一下這些代碼,了解它們后我們可以更準(zhǔn)確的去使用async/await,同時(shí)也能理解這種模式下異常和取消是怎樣完成的。

先來展示下用于分析反編譯代碼的例子,一個(gè)控制臺(tái)項(xiàng)目的代碼,這是能想到的展示異步方法最簡(jiǎn)單的例子了,而且和實(shí)際項(xiàng)目中常用的代碼結(jié)構(gòu)也差不太多:

//實(shí)體類 public?class?User {public?int?Id?{?get;?set;?}public?string?UserName?{?get;?set;?}?=?"hystar";public?string?Email?{?get;?set;?} }class?Program {static?void?Main(string[]?args){var?service?=?new?Service(new?Repository());var?name?=?service.GetUserName(1).Result;Console.WriteLine(name);} }public?class?Service {private?readonly?Repository?_repository;public?Service(Repository?repository){_repository?=?repository;}public?async?Task<string>?GetUserName(int?id){var?name?=?await?_repository.GetById(id);return?name;} }public?class?Repository {private?DbContext?_dbContext;private?DbSet<User>?_set;public?Repository(){_dbContext?=?new?DbContext("");_set?=?_dbContext.Set<User>();}public?async?Task<string>?GetById(int?id){?//IO...var?user?=?await?_set.FindAsync(id);return?user.UserName;} }

注意:控制臺(tái)版本的示例代碼中在Main函數(shù)中使用了task.Result來獲取異步結(jié)果,需要注意這是一種阻塞模式,在除控制臺(tái)之外的UI環(huán)境不要使用類似Result屬性這樣會(huì)阻塞的方法,它們會(huì)導(dǎo)致UI線程死鎖。而對(duì)于沒有SynchronizationContext的控制臺(tái)應(yīng)用確是再合適不過了。對(duì)于沒有返回值的Task,可以使用Wait()方法等待其完成。

這里使用ILSpy去查看反編譯后的代碼,而且注意要將ILSpy選項(xiàng)中的Decompile async methods (async/await)禁用(如下圖),否則ILSpy會(huì)很智能將IL反編譯為有async/await關(guān)鍵字的C#代碼。另外我也嘗試過Telerik JustDecompile等工具,但是能完整展示反編譯出的狀態(tài)機(jī)的只有ILSpy。

圖2

另外注意,應(yīng)該選擇Release版本的代碼去查看,這是在一個(gè)Stackoverflow回答中看到的,說是有啥不同,具體也沒仔細(xì)看,這里知道選擇Release版exe/dll反編譯就好了。下面以Service類為例來看一下反編譯后的代碼:

圖3

通過圖上的注釋可以看到代碼主要由兩大部分構(gòu)成,Service類原有的代碼和一個(gè)由編譯器生成的狀態(tài)機(jī),下面分別具體了解下它們都做了什么。依然是以圖片加注釋為主,重要的部分會(huì)在圖后給出文字說明。

圖4

通過上圖中的注釋可以大致了解GetUserName方法編譯后的樣子。我們?cè)敿?xì)介紹下其中幾個(gè)點(diǎn),首先是AsyncTaskMethodBuilder<T>,我感覺很有必要列出其代碼一看:

為了篇幅關(guān)系,這里刪除了部分復(fù)雜的實(shí)現(xiàn),取而代之的是介紹方法作用的注釋性文字,對(duì)于簡(jiǎn)單的方法或是重要的方法保留了代碼。

namespace?System.Runtime.CompilerServices {public?struct?AsyncTaskMethodBuilder<TResult>{internal?static?readonly?Task<TResult>?s_defaultResultTask?=?AsyncTaskCache.CreateCacheableTask<TResult>(default(TResult));//這也是一個(gè)很重要的類,AsyncTaskMethodBuilder將一些操作進(jìn)一步交給AsynchronousMethodBuilderCore來完成private?AsyncMethodBuilderCore?m_coreState;private?Task<TResult>?m_task;[__DynamicallyInvokable]public?Task<TResult>?Task{[__DynamicallyInvokable]get{Task<TResult>?task?=?this.m_task;if?(task?==?null){task?=?(this.m_task?=?new?Task<TResult>());}return?task;}}private?object?ObjectIdForDebugger{get{return?this.Task;}}[__DynamicallyInvokable]public?static?AsyncTaskMethodBuilder<TResult>?Create(){return?default(AsyncTaskMethodBuilder<TResult>);}//開始狀態(tài)機(jī)的執(zhí)行[__DynamicallyInvokable,?DebuggerStepThrough,?SecuritySafeCritical]public?void?Start<TStateMachine>(ref?TStateMachine?stateMachine)?where?TStateMachine?:?IAsyncStateMachine{if?(stateMachine?==?null){throw?new?ArgumentNullException("stateMachine");}//保存當(dāng)前ExecutionContext,這是很重要的一步,后文會(huì)具體介紹ExecutionContextSwitcher?executionContextSwitcher?=?default(ExecutionContextSwitcher);RuntimeHelpers.PrepareConstrainedRegions();try{ExecutionContext.EstablishCopyOnWriteScope(ref?executionContextSwitcher);stateMachine.MoveNext();}finally{executionContextSwitcher.Undo();}}[__DynamicallyInvokable]public?void?SetStateMachine(IAsyncStateMachine?stateMachine){this.m_coreState.SetStateMachine(stateMachine);}[__DynamicallyInvokable]public?void?AwaitOnCompleted<TAwaiter,?TStateMachine>(ref?TAwaiter?awaiter,?ref?TStateMachine?stateMachine)?where?TAwaiter?:?INotifyCompletion?where?TStateMachine?:?IAsyncStateMachine{try{AsyncMethodBuilderCore.MoveNextRunner?runner?=?null;Action?completionAction?=?this.m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn???this.Task?:?null,?ref?runner);if?(this.m_coreState.m_stateMachine?==?null){Task<TResult>?task?=?this.Task;this.m_coreState.PostBoxInitialization(stateMachine,?runner,?task);}awaiter.OnCompleted(completionAction);}catch?(Exception?arg_5C_0){AsyncMethodBuilderCore.ThrowAsync(arg_5C_0,?null);}}[__DynamicallyInvokable,?SecuritySafeCritical]public?void?AwaitUnsafeOnCompleted<TAwaiter,?TStateMachine>(ref?TAwaiter?awaiter,?ref?TStateMachine?stateMachine)?where?TAwaiter?:?ICriticalNotifyCompletion?where?TStateMachine?:?IAsyncStateMachine{try{AsyncMethodBuilderCore.MoveNextRunner?runner?=?null;//這是整個(gè)方法乃至類中最重要的一部分//獲取當(dāng)前狀態(tài)執(zhí)行完畢后下一步的操作Action?completionAction?=?this.m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn???this.Task?:?null,?ref?runner);if?(this.m_coreState.m_stateMachine?==?null){Task<TResult>?task?=?this.Task;this.m_coreState.PostBoxInitialization(stateMachine,?runner,?task);}//將下一步操作傳遞給awaiter對(duì)象,實(shí)際進(jìn)入下一步還是通過awaiter來進(jìn)行的。awaiter.UnsafeOnCompleted(completionAction);}catch?(Exception?arg_5C_0){AsyncMethodBuilderCore.ThrowAsync(arg_5C_0,?null);}}[__DynamicallyInvokable]public?void?SetResult(TResult?result){//設(shè)置結(jié)果//通過Task上的方法來完成}internal?void?SetResult(Task<TResult>?completedTask){//設(shè)置結(jié)果,調(diào)用上面的方法來完成????????????}public?void?SetException(Exception?exception){//設(shè)置異常//通過Task上的方法來實(shí)現(xiàn)}internal?void?SetNotificationForWaitCompletion(bool?enabled){this.Task.SetNotificationForWaitCompletion(enabled);}private?Task<TResult>?GetTaskForResult(TResult?result){//獲取Task包裝的結(jié)果}} }

狀態(tài)機(jī)的幾種狀態(tài)如下:

  • -1:表示還未開始執(zhí)行

  • -2:執(zhí)行結(jié)束,可能是正常完成,也可能遇到異常處理異常后結(jié)束

  • 0~:下一個(gè)狀態(tài)。如0表示初始的-1之后的下一個(gè)狀態(tài),1表示0后的下一狀態(tài),以此類推。

上面的類中還出現(xiàn)了一個(gè)很重要的類型AsyncMethodBuilderCore,簡(jiǎn)單的了解一下這個(gè)類型也很有必要。

namespace?System.Runtime.CompilerServices {internal?struct?AsyncMethodBuilderCore{internal?sealed?class?MoveNextRunner{private?readonly?ExecutionContext?m_context;internal?IAsyncStateMachine?m_stateMachine;[SecurityCritical]private?static?ContextCallback?s_invokeMoveNext;[SecurityCritical]internal?MoveNextRunner(ExecutionContext?context,?IAsyncStateMachine?stateMachine){this.m_context?=?context;this.m_stateMachine?=?stateMachine;}[SecuritySafeCritical]internal?void?Run(){//這個(gè)方法被包裝為“繼續(xù)執(zhí)行”委托實(shí)際執(zhí)行的代碼//這個(gè)方法最終要的作用是給繼續(xù)執(zhí)行的代碼設(shè)置正確的ExecutionContext}[SecurityCritical]private?static?void?InvokeMoveNext(object?stateMachine){((IAsyncStateMachine)stateMachine).MoveNext();}}private?class?ContinuationWrapper{internal?readonly?Action?m_continuation;private?readonly?Action?m_invokeAction;internal?readonly?Task?m_innerTask;internal?ContinuationWrapper(Action?continuation,?Action?invokeAction,?Task?innerTask){if?(innerTask?==?null){innerTask?=?AsyncMethodBuilderCore.TryGetContinuationTask(continuation);}this.m_continuation?=?continuation;this.m_innerTask?=?innerTask;this.m_invokeAction?=?invokeAction;}internal?void?Invoke(){this.m_invokeAction();}}internal?IAsyncStateMachine?m_stateMachine;internal?Action?m_defaultContextAction;public?void?SetStateMachine(IAsyncStateMachine?stateMachine){}//上文提到的獲取“繼續(xù)執(zhí)行”委托的方法//方法通過包裝內(nèi)部類MoveNextRunner的Run方法來實(shí)現(xiàn)[SecuritySafeCritical]internal?Action?GetCompletionAction(Task?taskForTracing,?ref?AsyncMethodBuilderCore.MoveNextRunner?runnerToInitialize){Debugger.NotifyOfCrossThreadDependency();ExecutionContext?executionContext?=?ExecutionContext.FastCapture();Action?action;AsyncMethodBuilderCore.MoveNextRunner?moveNextRunner;if?(executionContext?!=?null?&&?executionContext.IsPreAllocatedDefault){action?=?this.m_defaultContextAction;if?(action?!=?null){return?action;}moveNextRunner?=?new?AsyncMethodBuilderCore.MoveNextRunner(executionContext,?this.m_stateMachine);action?=?new?Action(moveNextRunner.Run);if?(taskForTracing?!=?null){action?=?(this.m_defaultContextAction?=?this.OutputAsyncCausalityEvents(taskForTracing,?action));}else{this.m_defaultContextAction?=?action;}}else{moveNextRunner?=?new?AsyncMethodBuilderCore.MoveNextRunner(executionContext,?this.m_stateMachine);action?=?new?Action(moveNextRunner.Run);if?(taskForTracing?!=?null){action?=?this.OutputAsyncCausalityEvents(taskForTracing,?action);}}if?(this.m_stateMachine?==?null){runnerToInitialize?=?moveNextRunner;}return?action;}private?Action?OutputAsyncCausalityEvents(Task?innerTask,?Action?continuation){}internal?void?PostBoxInitialization(IAsyncStateMachine?stateMachine,?AsyncMethodBuilderCore.MoveNextRunner?runner,?Task?builtTask){//初始化AsyncMethodBuilderCore中的狀態(tài)機(jī)變量。這里發(fā)生裝箱操作。}internal?static?void?ThrowAsync(Exception?exception,?SynchronizationContext?targetContext){//將異常與SynchronizationContext相關(guān)聯(lián)}internal?static?Action?CreateContinuationWrapper(Action?continuation,?Action?invokeAction,?Task?innerTask?=?null){return?new?Action(new?AsyncMethodBuilderCore.ContinuationWrapper(continuation,?invokeAction,?innerTask).Invoke);}internal?static?Action?TryGetStateMachineForDebugger(Action?action){//獲取用于調(diào)試目的的“繼續(xù)執(zhí)行”委托}internal?static?Task?TryGetContinuationTask(Action?action){//獲取“繼續(xù)執(zhí)行”的Task}} }

總結(jié)來說AsyncTaskMethodBuilder<T>和AsyncMethodBuilderCore控制著狀態(tài)機(jī)的執(zhí)行(主要是在正確的Context下調(diào)用MoveNext方法),并在執(zhí)行狀態(tài)機(jī)的過程中負(fù)責(zé)正確的設(shè)置ExecutionContext和SynchronizationContext。

介紹了這么多基礎(chǔ)構(gòu)造,你可能更關(guān)心原來的調(diào)用Repository的方法的代碼去哪了,它們?cè)跔顟B(tài)機(jī)的代碼中。下面就來看一下狀態(tài)機(jī):

圖5

通過注釋應(yīng)該可以了解這個(gè)狀態(tài)機(jī)的細(xì)節(jié)了。

簡(jiǎn)單的說一下這個(gè)struct優(yōu)化。一開始狀態(tài)機(jī)被作為struct對(duì)象放置在棧上,對(duì)于await的工作已經(jīng)完成不需要等待的情況,將快速結(jié)束狀態(tài)機(jī),這樣狀態(tài)機(jī)直接出棧效率高。如果await的工作需要等待則控制異步方法執(zhí)行的AsyncTaskMethodBuilder再將狀態(tài)機(jī)移動(dòng)到堆中。因?yàn)檫@種情況下會(huì)發(fā)生Context切換(在SynchronizationContext不為空的情況下),如果狀態(tài)機(jī)還在棧上則會(huì)導(dǎo)致很大的切換負(fù)擔(dān)。

其實(shí)搞成一個(gè)狀態(tài)機(jī)的目的主要還是考慮到可能存在多個(gè)await的情況。對(duì)于只有1個(gè)await的情況其實(shí)狀態(tài)機(jī)的必要性不大,幾個(gè)if也就夠了,下面擴(kuò)展下上面的例子看看有2個(gè)以上await(1個(gè)和2個(gè)await的狀態(tài)機(jī)都是使用if/else解決問題,從3個(gè)起開始不同)時(shí)編譯器產(chǎn)生的代碼,首先是擴(kuò)展后的C#代碼(以WPF應(yīng)用為例):

public?partial?class?MainWindow?:?Window {public?MainWindow(){InitializeComponent();}private?async?void?Button_Click(object?sender,?RoutedEventArgs?e){var?userService?=?new?Service();Debug.Write(Thread.CurrentThread.ManagedThreadId);var?avatar?=?await?userService.GetUserAvatarAsync(1);Debug.Write(Thread.CurrentThread.ManagedThreadId);//使用獲取的avatar} }public?class?Service {private?readonly?Repository?_repository;private?readonly?WebHepler?_webHelpler;private?readonly?ImageLib?_imgLib;public?Service(){_repository?=?new?Repository();_webHelpler?=?new?WebHepler();_imgLib?=?new?ImageLib();}public?async?Task<byte[]>?GetUserAvatarAsync(int?id){Debug.WriteLine("Service--"?+?Thread.CurrentThread.ManagedThreadId);var?user?=?await?_repository.GetByIdAsync(id);Debug.WriteLine("Service--"?+?Thread.CurrentThread.ManagedThreadId);var?email?=?user.Email;var?avatar?=?await?_webHelpler.GetAvatarByEmailAsync(email);Debug.WriteLine("Service--"?+?Thread.CurrentThread.ManagedThreadId);var?thumbnail?=?await?_imgLib.GetImgThumbnailAsync(avatar);return?thumbnail;} }public?class?Repository {private?readonly?DbContext?_dbContext;private?readonly?DbSet<User>?_set;public?Repository(){//_dbContext?=?new?DbContext("");//_set?=?_dbContext.Set<User>();}public?async?Task<User>?GetByIdAsync(int?id){Debug.WriteLine("Repo--"?+?Thread.CurrentThread.ManagedThreadId);//IO...var?user?=?await?_set.FindAsync(id);Debug.WriteLine("Repo--"?+?Thread.CurrentThread.ManagedThreadId);return?user;} }public?class?WebHepler {private?readonly?HttpClient?_httpClient;public?WebHepler(){_httpClient?=?new?HttpClient();}public?async?Task<byte[]>?GetAvatarByEmailAsync(string?email){Debug.WriteLine("Http--"?+?Thread.CurrentThread.ManagedThreadId);var?url?=?"http://avater-service-sample/"?+?email;var?resp?=?await?_httpClient.GetByteArrayAsync(url);Debug.WriteLine("Http--"?+?Thread.CurrentThread.ManagedThreadId);return?resp;} }public?class?ImageLib {public?async?Task<byte[]>?GetImgThumbnailAsync(byte[]?avatar){//模擬一個(gè)異步圖像處理任務(wù)return?await?Task.Run(()?=>{Task.Delay(500);return?avatar;});} }

依然以Service類為例來分析await編譯后的樣子:

Service中的GetUserAvatar方法中的3個(gè)await將把函數(shù)體分割為4個(gè)異步區(qū)間,如下:

圖6

編譯生成的代碼最主要的不同是生成的狀態(tài)機(jī)變了,依舊是通過截圖和注釋來說一下這個(gè)新的狀態(tài)機(jī)的執(zhí)行情況(方便對(duì)比,注釋將只標(biāo)出與之前狀態(tài)機(jī)不同的部分):

圖7

通過上面的分析,async/await關(guān)鍵字背后的秘密已經(jīng)清清楚楚。下面來說一下線程的問題。

?

線程!

關(guān)于async/await模式線程的問題,剛開始學(xué)習(xí)async/await那陣,看到很多文章,各種各樣的說法,一度讓我很迷惑。

一種觀點(diǎn)是很多國(guó)外同行的文章里說的:async/await本身不創(chuàng)建線程。StackoverFlow上很多回答也明確說async/await這兩個(gè)新增的關(guān)鍵字只是語法糖,編譯后的代碼不新建線程,這曾經(jīng)一度給我造成了很大的困惑:“不創(chuàng)建線程的話要異步還有啥用!”。

后來看到一種觀點(diǎn)是園友jesse2013博文中的一句話:

await 不會(huì)開啟新的線程,當(dāng)前線程會(huì)一直往下走直到遇到真正的Async方法(比如說HttpClient.GetStringAsync),這個(gè)方法的內(nèi)部會(huì)用Task.Run或者Task.Factory.StartNew 去開啟線程。也就是如果方法不是.NET為我們提供的Async方法,我們需要自己創(chuàng)建Task,才會(huì)真正的去創(chuàng)建線程。

這個(gè)這個(gè)觀點(diǎn)應(yīng)該是正確的,可后來看了很多代碼后感覺還不完全是這樣,畢竟一個(gè)被調(diào)用的async方法就會(huì)產(chǎn)生一個(gè)新的Task,而這個(gè)新的Task可能去“開啟一個(gè)新線程”。改造下上面的代碼測(cè)試這個(gè)問題:

public?class?Service {private?readonly?Repository?_repository;public?Service(Repository?repository){_repository?=?repository;}public?async?Task<string>?GetUserName(int?id){Console.WriteLine(Thread.CurrentThread.ManagedThreadId);var?name?=?await?_repository.GetById(id);Console.WriteLine(Thread.CurrentThread.ManagedThreadId);return?name;} }public?class?Repository {private?DbContext?_dbContext;private?DbSet<User>?_set;public?Repository(){_dbContext?=?new?DbContext("");_set?=?_dbContext.Set<User>();}public?async?Task<string>?GetById(int?id){????????//IO...var?user?=?await?_set.FindAsync(id);return?user.UserName;} }

在控制臺(tái)應(yīng)用中執(zhí)行這段代碼會(huì)發(fā)現(xiàn)輸出的兩個(gè)線程Id是不相同的。

提示:控制臺(tái)引用程序沒有SynchronizationContext,在不恢復(fù)SynchronizationContext的情況下能更好的看出線程的變化。

到底情況是怎樣的呢,這里試著分析下我的想法:

這里先闡釋清“創(chuàng)建新線程”這個(gè)概念。我認(rèn)為在這種情況下大家說的“創(chuàng)建新線程”可以被認(rèn)為是與調(diào)用方法使用不同的線程,這個(gè)線程可能是線程池已有的,也可能是新建并被加入到線程池的線程。明確這給之后,繼續(xù)說線程問題。

首先肯定一點(diǎn)async/await關(guān)鍵字不會(huì)創(chuàng)建新線程是對(duì)的。如上文代碼中所示async/await被編譯為一個(gè)狀態(tài)機(jī)的確不參與Task的創(chuàng)建,實(shí)際新建Task的是被調(diào)用的異步方法。也就是說每調(diào)用一次異步方法(每一個(gè)await)都會(huì)產(chǎn)生一個(gè)新的Task,這個(gè)Task會(huì)自動(dòng)執(zhí)行。前面說過Task由TaskScheduler安排執(zhí)行,一般都會(huì)在一個(gè)與調(diào)用線程不同的線程上執(zhí)行。

為了把這個(gè)問題解釋清楚,假設(shè)調(diào)用異步方法的線程為A,異步方法啟動(dòng)后在B線程執(zhí)行。當(dāng)B線程開始執(zhí)行后,A線程將交出控制權(quán)。異步方法執(zhí)行結(jié)束后,后續(xù)代碼(await后面的代碼)將在B線程上使用A線程的ExecutionContext(和SynchronizationContext,默認(rèn)情況)繼續(xù)執(zhí)行。

注意這個(gè)A線程到B線程控制權(quán)的轉(zhuǎn)換正是async異步模式的精髓之一。在WPF等這樣的客戶端環(huán)境這樣做不會(huì)阻塞UI線程,使界面不失去響應(yīng)。在MVC這樣的Web環(huán)境可以及時(shí)釋放HTTP線程,使Web服務(wù)器可以接收更多請(qǐng)求。畢竟B線程這種線程池中的線程成本更低。這樣就是為什么既然也要花等待異步操作完成的時(shí)間,還要另外使用異步方法的原因 - 及時(shí)釋放調(diào)用線程,讓低成本的線程去處理耗時(shí)的任務(wù)。

最后當(dāng)需要在發(fā)起執(zhí)行的線程(這里是A線程)上繼續(xù)進(jìn)行處理時(shí)只要獲得當(dāng)時(shí)A線程的ExecutionContext和SynchronizationContext就可以了,并在這些Context完成剩余操作即可。

如果后續(xù)還有其他await,則會(huì)出現(xiàn)C線程,D線程等。如B調(diào)用了C的話,B的各種Context會(huì)被傳遞給C。當(dāng)從異步方法返回后,執(zhí)行的線程變了但是Context沒變。這樣異步方法給我們的感覺就像是同步一般。這也就是async/await方法的精妙之處。

那個(gè)Task的ConfigureAwait方法又是做什么用的呢,理解了上文就很好理解這個(gè)方法了。在異步方法返回時(shí),會(huì)發(fā)生線程切換,默認(rèn)情況下(ConfigureAwait(true)時(shí))ExecutionContext和SynchronizationContext都會(huì)被傳遞。如果ConfigureAwait(false)則只有ExecutionContext會(huì)被傳遞,SynchronizationContext不會(huì)被傳遞。在WPF等客戶端程序UI部分,應(yīng)該使用默認(rèn)設(shè)置讓SynchronizationContext保持傳遞,這樣異步代碼的后續(xù)代碼才能正常操作UI。除此之外的其他情況,如上面的Service類中,都該使用ConfigureAwait(false)以放棄SynchronizationContext的傳遞來提高性能。

下面以圖應(yīng)該會(huì)對(duì)上面這段文字有更深的了解:

吐槽一下,本來是想用vs生成的時(shí)序圖進(jìn)行演示呢。結(jié)果發(fā)現(xiàn)vs2015取消這個(gè)功能了。手頭也沒有其他版本的vs。就用代碼截圖來掩飾這個(gè)線程變化過程吧。

首先是控制臺(tái)程序的線程變化情況:

圖8

因?yàn)榭刂婆_(tái)應(yīng)用沒有SynchronizationContext,所以可以清楚的看到線程的變化。

下面看看在WPF中類似流程執(zhí)行的樣子:

圖9

可以看到在默認(rèn)情況下每個(gè)await后的異步代碼返回到都回到UI線程,即所有await的后繼代碼都使用UI線程的SynchronizationContext來執(zhí)行。除了調(diào)用方法外,其它所有的方法沒有必要返回UI線程,所以我們應(yīng)該把除調(diào)用開始處(即Button_Click方法)外的所有異步調(diào)用都配置為ConfigureAwait(false)。

public?partial?class?MainWindow?:?Window {public?MainWindow(){InitializeComponent();}private?async?void?Button_Click(object?sender,?RoutedEventArgs?e){var?userService?=?new?Service();Debug.Write(Thread.CurrentThread.ManagedThreadId);var?avatar?=?await?userService.GetUserAvatarAsync(1);Debug.Write(Thread.CurrentThread.ManagedThreadId);//使用獲取的avatar} }public?class?Service {private?readonly?Repository?_repository;private?readonly?WebHepler?_webHelpler;public?Service(){_repository?=?new?Repository();_webHelpler?=?new?WebHepler();}public?async?Task<byte[]>?GetUserAvatarAsync(int?id){var?user?=?await?_repository.GetByIdAsync(id).ConfigureAwait(false);var?email?=?user.Email;var?avatar?=?await?_webHelpler.GetAvatarByEmailAsync(email).ConfigureAwait(false);return?avatar;} }public?class?Repository {private?readonly?DbContext?_dbContext;private?readonly?DbSet<User>?_set;public?Repository(){_dbContext?=?new?DbContext("");_set?=?_dbContext.Set<User>();}public?async?Task<User>?GetByIdAsync(int?id){//IO...var?user?=?await?_set.FindAsync(id).ConfigureAwait(false);return?user;} }public?class?WebHepler {private?readonly?HttpClient?_httpClient;public?WebHepler(){_httpClient?=?new?HttpClient();}public?async?Task<byte[]>?GetAvatarByEmailAsync(string?email){var?url?=?"http://avater-service-sample/"?+?email;var?resp?=?await?_httpClient.GetByteArrayAsync(url);return?resp;} }

通過上面的圖,可以了解到有SynchronizationContext和沒有SynchronizationContext環(huán)境的不同,是否恢復(fù)SynchronizationContext的影響。對(duì)于ASP.NET環(huán)境雖然也有SynchronizationContext,但實(shí)測(cè)線程切換的表現(xiàn)比較詭異,實(shí)在無法具體分析,但按照WPF的方式來配置異步肯定是對(duì)的。

其它資料:據(jù)CLR via C#作者大神Jeffrey Richter在書中所說,.NET這種以狀態(tài)機(jī)實(shí)現(xiàn)異步的思想來自于其為.NET 4.0寫的Power Threading庫(kù)中的AsyncEnumerator類。可以將其作為一個(gè)參考來學(xué)習(xí)async異步方法的機(jī)制。

async異步編程中的取消和進(jìn)度報(bào)告

由文章開始處的圖1可知,Task天生支持取消,通過一個(gè)接收CancellationToken的重載創(chuàng)建的Task可以被通知取消。

var?tokenSource?=?new?CancellationTokenSource(); CancellationToken?ct?=?tokenSource.Token; var?task?=?Task.Run(()?=>?Task.Delay(10000,ct),?ct); tokenSource.Cancel();

自然我們異步方法的取消也離不開CancellationToken,方法就是給異步方法添加接收CancellationToken的重載,如前文示例代碼Service中的方法可以添加一個(gè)這樣的重載支持取消:

public?async?Task<byte[]>?GetUserAvatarAsync(int?id,?CancellationToken?ct) {... }

async異步編程最大的一個(gè)特點(diǎn)就是傳播性,即如果有一個(gè)異步方法,則所有調(diào)用這個(gè)方法的方法都應(yīng)該是異步方法,而不能有任何同步方法(控制臺(tái)應(yīng)用Main函數(shù)中那種把異步轉(zhuǎn)同步的方式除外)。而通過CancellationToken實(shí)現(xiàn)的取消模式可以很好的適配這種傳播性,所需要做的就是把所有異步方法都添加支持CancellationToken的重載。之前的例子改造成支持取消后如下(展示一部分):

class?Program {static?void?Main(string[]?args){var?tokenSource?=?new?CancellationTokenSource();CancellationToken?ct?=?tokenSource.Token;var?userService?=?new?Service();var?avatar?=?userService.GetUserAvatarAsync(1,ct).Result;tokenSource.Cancel();Console.Read();} }public?class?Service {private?readonly?Repository?_repository;private?readonly?WebHepler?_webHelpler;public?Service(){_repository?=?new?Repository();_webHelpler?=?new?WebHepler();}public?async?Task<byte[]>?GetUserAvatarAsync(int?id,?CancellationToken?ct){var?user?=?await?_repository.GetByIdAsync(id,?ct);var?email?=?user.Email;ct.ThrowIfCancellationRequested();var?avatar?=?await?_webHelpler.GetAvatarByEmailAsync(email,?ct);return?avatar;} }

注意ct.ThrowIfCancellationRequested()調(diào)用,這是可以及時(shí)取消后續(xù)未完成代碼的關(guān)鍵。當(dāng)執(zhí)行這個(gè)語句時(shí),如果ct被標(biāo)記取消,則這個(gè)語句拋出OperationCanceledException異常,后續(xù)代碼停止執(zhí)行。

和取消機(jī)制一樣,新版的.NET也為進(jìn)度通知提供了內(nèi)置類型的支持。IProgress<T>和Progress<T>就是為此而生。類型中的泛型參數(shù)T表示Progress的ProgressChanged事件訂閱的處理函數(shù)的第二個(gè)參數(shù)的類型。擴(kuò)展之前的例子,把它改成支持進(jìn)度報(bào)告的方法:

class?Program {static?void?Main(string[]?args){var?progress?=?new?Progress<int>();progress.ProgressChanged?+=?(?s,?e?)?=>?{//e就是int類型的進(jìn)度,可以使用各種方式進(jìn)行展示。};var?userService?=?new?Service();var?avatar?=?userService.GetUserAvatarAsync(1,progress).Result;tokenSource.Cancel();Console.Read();} }public?class?Service {private?readonly?Repository?_repository;private?readonly?WebHepler?_webHelpler;public?Service(){_repository?=?new?Repository();_webHelpler?=?new?WebHepler();}public?async?Task<byte[]>?GetUserAvatarAsync(int?id,?IProgress<int>?progress){var?user?=?await?_repository.GetByIdAsync(id,?progress);//progress可以進(jìn)一步傳遞,但注意進(jìn)度值要在合理范圍內(nèi)var?email?=?user.Email;progress.Report(50);//報(bào)告進(jìn)度var?avatar?=?await?_webHelpler.GetAvatarByEmailAsync(email,?progress);progress.Report(100);return?avatar;} }

可以看到在async異步模式下取消和進(jìn)度都很容易使用。

?

以上介紹了擁有async/await支持的TAP異步編程。在編寫新的異步代碼時(shí)應(yīng)該優(yōu)先選用TAP模型,而且新版的.NET庫(kù)幾乎給所有同步接口增加了這種可以通過async/await使用的異步接口。但往往項(xiàng)目中會(huì)存在一些使用APM或EAP模式的代碼,通過下面介紹的一些方法可以使用async/await的方式調(diào)用這些代碼。

將BeginXXX/EndXXX的APM模式代碼轉(zhuǎn)為async異步方法只需要利用TaskFactory類的FromAsync方法即可,我們以介紹APM時(shí)提到的HttpWebRequest為例:

public?Task<WebResponse>?GetResponseAsync(WebRequest?client) {return?Task<WebResponse>.Factory.FromAsync(client.BeginGetResponse,?client.EndGetResponse,?null); }

TaskFactory的FromAsync方法中使用TaskCompletionSource<T>來構(gòu)造Task對(duì)象。

封裝EAP模式的代碼要比APM麻煩一些,我們需要手動(dòng)構(gòu)造TaskCompletionSource對(duì)象(代碼來自,手打的)。

WebClient?client; Uri?address; var?tcs?=?new?TaskCompletionSource<string>(); DownloadStringCompletedEventHandler?hander?=?null; handler?=?(_,?e)=> {client.DownloadStringCompleted?-=?handler;if(e.Cancelled)tcs.TrySetCanceled();else?if(e.Error?!=?null)tcs.TrySetException(e.Error);elsetcs.TrySetResult(e.Result); } client.DownloadStringCompleted?+=?handler; client.DownloadStringAsync(address);return?tcs.Task;

可以看到TaskCompletionSource提供了一種手動(dòng)指定Task結(jié)果來構(gòu)造Task的方式。

?

上面寫了那么多,真沒有信息保證全部都是正確的。最后推薦3篇文章,相信它們對(duì)理解async異步方法會(huì)有很大幫助,本文的很多知識(shí)點(diǎn)也是來自這幾篇文章:

  • Understanding C# async / await (1) Compilation

  • Understanding C# async / await (2) Awaitable-Awaiter Pattern

  • Understanding C# async / await (3) Runtime Context

?

WinRT 異步編程 C#

WinRT是完全不同于.NET的一種框架,目地就是把Windows的底層包裝成API讓各種語言都可以簡(jiǎn)單的調(diào)用。WinRT中對(duì)異步的實(shí)現(xiàn)也和.NET完全不同,這一小節(jié)先看一下WinRT中異步機(jī)制的實(shí)現(xiàn)方法,再來看一下怎樣使用C#和.NET與WinRT中的異步API進(jìn)行交互。

前文提到async異步編程中兩個(gè)比較重要的對(duì)象是awaitable和awaiter。在WinRT中充當(dāng)awaitable的是IAsyncInfo接口的對(duì)象,具體使用中有如下4個(gè)實(shí)現(xiàn)IAsyncInfo接口的類型:

  • IAsyncAction

  • IAsyncActionWithProgress<TProgress>

  • IAsyncOperation<TResult>

  • IAsyncOperationWithProgress<TResult, TProgress>

由泛型參數(shù)可以看出Action和Operation結(jié)尾的兩個(gè)類型不同之處在于IAsyncAction的GetResults方法返回void,而IAsyncOperation<TResult>的GetResults方法返回一個(gè)對(duì)象。WithProgress結(jié)尾的類型在類似類型的基礎(chǔ)上增加了進(jìn)度報(bào)告功能(它們內(nèi)部定義了Progress事件用來執(zhí)行進(jìn)度變更時(shí)的處理函數(shù))。

Task和IAsyncInfo分別是對(duì).NET和WinRT中異步任務(wù)的包裝。它們的原理相同但具體實(shí)現(xiàn)有所不同。IAsyncInfo表示的任務(wù)的狀態(tài)(可以通過Status屬性查詢)有如下幾種(和Task對(duì)照,整理自MSDN):

Task狀態(tài)

(TaskStatus類型)

IAsyncInfo狀態(tài)

(AsyncStatus類型)

RanToCompletion

Completed

Faulted

Error

Canceled

Canceled

所有其他值和已請(qǐng)求的取消

Canceled

所有其他值和未請(qǐng)求的取消

Started

另外獲取異常的方式也不一樣,通過Task中的Exception屬性可以直接得到.NET異常,而IAsynInfo中錯(cuò)誤是通過ErrorCode屬性公開的一個(gè)HResult類型的錯(cuò)誤碼。當(dāng)時(shí)用下文價(jià)紹的方法將IAsynInfo轉(zhuǎn)為Task時(shí),HResult會(huì)被映射為.NET Exception。

之前我們說這些IAsyncXXX類型是awaitable的,但為什么這些類型中沒有GetAwaiter方法呢。真相是GetAwaiter被作為定義在.NET的程序集System.Runtime.WindowsRuntime.dll中的擴(kuò)展方法,因?yàn)榛旧蟻碚fasync/awati還是C#使用的關(guān)鍵字,而C#主要以.NET為主。

這些擴(kuò)展方法聲明形如(有多個(gè)重載,下面是其中2個(gè)):

public?static?TaskAwaiter?GetAwaiter<TResult>(this?IAsyncAction?source); public?static?TaskAwaiter<TResult>?GetAwaiter<TResult,?TProgress>(this?IAsyncOperationWithProgress<TResult,?TProgress>?source);

我們又見到了熟悉的TaskAwaiter。這個(gè)方法的實(shí)現(xiàn)其實(shí)也很簡(jiǎn)單(以第一個(gè)重載為例):

public?static?TaskAwaiter?GetAwaiter(this?IAsyncAction?source) {return?WindowsRuntimeSystemExtensions.AsTask(source).GetAwaiter(); }

可以看到就是通過task.GetAwaiter得到的TaskAwaiter對(duì)象。

這一系列擴(kuò)展方法的背后又有一個(gè)更重要的擴(kuò)展方法 - AsTask()。

AsTask方法有更多的重載,其實(shí)現(xiàn)原理和前文介紹將EAP包裝為async異步模式的代碼差不多,都是通過TaskCompletionSource來手工構(gòu)造Task。下面展示的是一個(gè)最復(fù)雜的重載的實(shí)現(xiàn):

public?static?Task<TResult>?AsTask<TResult,?TProgress>(this?IAsyncOperationWithProgress<TResult,?TProgress>?source,?CancellationToken?cancellationToken,?IProgress<TProgress>?progress) {if?(source?==?null)throw?new?ArgumentNullException("source");TaskToAsyncOperationWithProgressAdapter<TResult,?TProgress>?withProgressAdapter?=?source?as?TaskToAsyncOperationWithProgressAdapter<TResult,?TProgress>;if?(withProgressAdapter?!=?null?&&?!withProgressAdapter.CompletedSynchronously){Task<TResult>?task?=?withProgressAdapter.Task?as?Task<TResult>;if?(!task.IsCompleted){if?(cancellationToken.CanBeCanceled?&&?withProgressAdapter.CancelTokenSource?!=?null)WindowsRuntimeSystemExtensions.ConcatenateCancelTokens(cancellationToken,?withProgressAdapter.CancelTokenSource,?(Task)?task);if?(progress?!=?null)WindowsRuntimeSystemExtensions.ConcatenateProgress<TResult,?TProgress>(source,?progress);}return?task;}switch?(source.Status){case?AsyncStatus.Completed:return?Task.FromResult<TResult>(source.GetResults());case?AsyncStatus.Canceled:return?Task.FromCancellation<TResult>(cancellationToken.IsCancellationRequested???cancellationToken?:?new?CancellationToken(true));case?AsyncStatus.Error:return?Task.FromException<TResult>(RestrictedErrorInfoHelper.AttachRestrictedErrorInfo(source.get_ErrorCode()));default:if?(progress?!=?null)WindowsRuntimeSystemExtensions.ConcatenateProgress<TResult,?TProgress>(source,?progress);AsyncInfoToTaskBridge<TResult,?TProgress>?infoToTaskBridge?=?new?AsyncInfoToTaskBridge<TResult,?TProgress>();try{source.Completed?=?new?AsyncOperationWithProgressCompletedHandler<TResult,?TProgress>(infoToTaskBridge.CompleteFromAsyncOperationWithProgress);infoToTaskBridge.RegisterForCancellation((IAsyncInfo)?source,?cancellationToken);}catch{if?(Task.s_asyncDebuggingEnabled)Task.RemoveFromActiveTasks(infoToTaskBridge.Task.Id);throw;}return?infoToTaskBridge.Task;} }

通過參數(shù)可以看到,這個(gè)轉(zhuǎn)換Task的過程支持調(diào)用方法傳入的取消和進(jìn)度報(bào)告。如果我們需要調(diào)用的WinRT異步方法的過程中支持取消和進(jìn)度報(bào)告,就不能直接await那個(gè)異步方法(相當(dāng)于調(diào)用了默認(rèn)無參的AsTask的返回task上的GetAwaiter方法),而是應(yīng)該await顯示調(diào)用的AsTask(可以傳入CancellationToken及IProgress參數(shù)的重載,上面那個(gè))返回的task對(duì)象。這個(gè)可以見本小節(jié)末尾處的例子。

回頭看一下上面給出的AsTask的實(shí)現(xiàn)。里面一個(gè)最終要的對(duì)象就是TaskToAsyncOperationWithProgressAdapter<TResult, TProgress>,其可以由IAsyncOperationWithProgress<TResult, TProgress>直接轉(zhuǎn)型而來。它也是IAsyncOperationWithProgress<TResult, TProgress>和Task之間的一個(gè)橋梁。這個(gè)類的工作主要由其父類TaskToAsyncInfoAdapter<TCompletedHandler, TProgressHandler, TResult, TProgressInfo>來完成。這個(gè)父類的實(shí)現(xiàn)就比較復(fù)雜了,但道理都是相同的。有興趣的同學(xué)自行查看其實(shí)現(xiàn)吧。

?

了解了原理最后來看一下代碼示例,WinRT中所有的IO相關(guān)的類中只提供異步方法,示例因此也選擇了這個(gè)使用最廣泛的功能(示例代碼來源是某開源庫(kù),具體是啥忘了,有輕微改動(dòng)):

public?async?Task<string>?ReadTextAsync(string?filePath) {var?text?=?string.Empty;using?(var?stream?=?await?ReadFileAsync(filePath)){using?(var?reader?=?new?StreamReader(stream)){text?=?await?reader.ReadToEndAsyncThread();}}return?text; }

有了async/await和上文介紹的擴(kuò)展方法的支持,C#調(diào)用WinRT的異步接口和使用.NET中的異步接口一樣的簡(jiǎn)單。

如果是需要傳遞取消和進(jìn)度報(bào)告怎么辦呢?

public?async?Task<string>?ReadTextAsync(string?filePath,?CancellationToken?ct,?IProgress<int>?progress) {var?text?=?string.Empty;try{using?(var?stream?=?await?ReadFileAsync(filePath).AsTask(ct,?progress)){using?(var?reader?=?new?StreamReader(stream)){text?=?await?reader.ReadToEndAsyncThread().AsTask(ct,?progress);}}}catch(OperationCanceledException)?{...}return?text; }

代碼的簡(jiǎn)潔程度讓你感到震撼吧。而且得到Task對(duì)象后,不但可以方便的配置取消和進(jìn)度報(bào)告,還能通過ConfigureAwait來配置SynchronizationContext的恢復(fù)。

不知道參數(shù)ct和progress怎么來的同學(xué)可以看上一小節(jié)的取消和異步部分。

除了由IAsyncInfo到Task的轉(zhuǎn)換外,還可以由Task/Task<T>轉(zhuǎn)為IAsyncAction/IAsyncOperation<T>。這個(gè)轉(zhuǎn)換的主要作用是把C#寫的代碼封裝為WinRT供其它語言調(diào)用。實(shí)現(xiàn)這個(gè)操作的AsAsyncAction/AsAsyncOperation<T>方法也是定義于上面提到的System.Runtime.WindowsRuntime.dll程序集中。以本文第一小節(jié)的Service類為例,將其GetUserName方法改造成返回IAsyncOperation<string>的方法,如下:

public?class?Service {private?readonly?Repository?_repository;public?Service(Repository?repository){_repository?=?repository;}public?IAsyncOperation<string>?GetUserName(int?id){var?nameAsync?=?_repository.GetByIdAsync(id).AsAsyncOperation();return?nameAsync;} }

這兩個(gè)擴(kuò)展方法是用簡(jiǎn)單方便,但有一點(diǎn)不足的就是不能支持Task中的取消和進(jìn)度報(bào)告。要解決這個(gè)問題可以使用IAsyncInfo的Run方法來獲得IAsynInfo對(duì)象。Run方法支持多種不同類型的委托對(duì)象作為參數(shù),比較復(fù)雜的一種可以支持取消和進(jìn)度報(bào)告作為委托對(duì)象(一般是lambda表達(dá)式)的參數(shù),比如把上面的例子改成支持取消和進(jìn)度報(bào)告后如下:

public?class?Service {private?readonly?Repository?_repository;public?Service(Repository?repository){_repository?=?repository;}private?async?Task<string>?GetUserNameInternal(int?id,?){var?name?=?await?_repository.GetByIdAsync(id,?ct,?progress);return?name;}public?IAsyncOperation<string>?GetUserName(int?id,?CancellationToken?ct,?IProgress<int>?progress){var?nameAsync?=?AsyncInfo.Run(async?(ct,?progress)=>{?var?name?=?await?GetUserNameInternal(id,?ct,?progress);return?name;};return?nameAsync;} }

內(nèi)幕這樣就輕松的實(shí)現(xiàn)了將C#編寫的代碼作為WinRT組件的過程。從如下AsAsyncOperation和AsyncInfo.Run的反編譯代碼來看,很難知道這個(gè)方法的實(shí)現(xiàn)細(xì)節(jié),畢竟它們都是和WinRT Native代碼相關(guān)的部分。

public?static?IAsyncOperation<TResult>?AsAsyncOperation<TResult>(this?Task<TResult>?source) {return?(IAsyncOperation<TResult>)?null; }public?static?IAsyncAction?Run(Func<CancellationToken,?Task>?taskProvider) {return?(IAsyncAction)?null; }

?

WinRT異步編程 C++

微軟對(duì)C++進(jìn)行了擴(kuò)展,一方面是為C++實(shí)現(xiàn)類似C#中基于Task的線程管理方式,另一方面讓C++(準(zhǔn)確說是C++/CX)可以實(shí)現(xiàn)與WinRT規(guī)范的的異步接口互操作。

這些擴(kuò)展主要定義于ppltask.h中,concurrency命名空間下。

concurrency::task

先來看一下和.NET Task基本等價(jià)的task類型。這也是微軟C++擴(kuò)展中并發(fā)異步線程管理的核心類型之一。微軟圍繞concurrency::task的設(shè)計(jì)的一些方法與C#中的Task相關(guān)方法真的非常下。下面的表格對(duì)比了C#的Task與C++中的concurrency::task。有C# Task基礎(chǔ)的話,對(duì)于concurrency::task很容易就能上手。

?

?C# TaskC++ concurrency::task
構(gòu)造 方式1constructorconstructor
構(gòu)造 方式2Task.Factory.StartNew()

用于異步 - create_task()

構(gòu)造 方式3

用于并行 -?make_task()

返回task_handle,和task_group等同用。

阻塞 - 等待完成task.Wait()task::wait()
阻塞 - 等待獲取結(jié)果GetAwaiter().GetResult()task::get()
任務(wù)狀態(tài)類型TaskStatusconcurrency::task_status
并行 - 等待全部Task.WhenAll()concurrency::when_all
并行 - 等待部分Task.WhenAny()concurrency::when_any
異步 - 任務(wù)延續(xù)Task.ContinueWith()task::then()

?

接著討論一下本節(jié)的重點(diǎn)內(nèi)容,微軟給C++帶來的異步支持。

普通異步

看過之前介紹C#異步的部分,可以知道支持異步的系統(tǒng)無非就由以下以下幾部分組成:任務(wù)創(chuàng)建、任務(wù)延續(xù)、任務(wù)等待、取消、進(jìn)度報(bào)告等。依次來看一下ppltask.h中支持這些部分的方法。

create_task方法可以將函數(shù)對(duì)象(廣義上的函數(shù)對(duì)象包含如lambda表達(dá)式,在C++11中也多用lambda表達(dá)式作為函數(shù)對(duì)象)包裝成task類對(duì)象。如上文所述,定義在ppltask.h中,位于concurrency命名空間下的task類和異步方法關(guān)系最密切。下面的代碼示例了concurrency::task的創(chuàng)建。

?
  • task<int>?op1?=?create_task([]()??

  • {??

  • ?????return?0;??

  • });

  • 在C++11中一般都使用auto直接表示一些復(fù)雜的類型,讓編譯器去推斷。例子中寫出完整的類型可以讓讀者更好的理解方法的返回類型。

    而類似于.NET Task中的ContinueWith方法的task::then方法,基本使用如下:

    ?
  • op1.then([](int?v){??

  • ?????return?0;??

  • });

  • 在C++中由于沒有類似C#中async/await關(guān)鍵字的支持,所以后續(xù)任務(wù)不能像C#中那樣直接跟在await ...語句后,必須通過task::then方法來設(shè)置。

    then方法也可以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,如:

    ?
  • auto?t?=?create_task([]()??

  • {??

  • ?????//do?something??

  • }).then([](int?v){??

  • ?????return?0;??

  • });

  • 關(guān)于后續(xù)代碼執(zhí)行上下文的問題,如果create_task方法接受的函數(shù)對(duì)象返回的是task<T>或task<void>則后續(xù)代碼會(huì)在相同的線程上下文運(yùn)行,如果返回的是T或void則后續(xù)任務(wù)會(huì)在任意上下文運(yùn)行。可以使用concurrency::task_continuation_context來更改這個(gè)設(shè)置。具體用法是將task_continuation_context傳給task::then其中那些接受task_continuation_context類型參數(shù)的重載。如果參數(shù)值為concurrency::task_continuation_context::use_arbitrary,則表示指定延續(xù)在后臺(tái)線程上運(yùn)行,如果參數(shù)值為concurrency::task_continuation_context::use_current,則表示指定延續(xù)在調(diào)用了task::then的線程上運(yùn)行。如:

    ?

    ?
  • auto?t?=?create_task([]()??

  • {??

  • ?????//do?something??

  • }).then([](int?v){??

  • ?????//do?something?else;??

  • },task_continuation_context::use_arbitrary());//then()中傳入的代碼將在后臺(tái)線程執(zhí)行,相對(duì)于C#中配置ConfigAwait(false)。

  • 對(duì)于取消和異步的支持,將在下一小段進(jìn)行介紹,那里的實(shí)現(xiàn)方式同樣可以應(yīng)用到這一部分中。

    使用create_task的方式創(chuàng)建task的方法只用于C++內(nèi)部對(duì)task的管理。如果是希望將異步作為WinRT組件發(fā)布需要使用下面介紹的create_async。

    如果是純C++中處理多線程任務(wù),除了使用Windows中所提供的task,還可以考慮C++11標(biāo)準(zhǔn)庫(kù)中的thread,后者跨平臺(tái)更好。后文會(huì)有一部分介紹C++11的thread。如果是對(duì)C#的TPL模型很熟悉,轉(zhuǎn)到C++使用ppltask.h中的task會(huì)發(fā)現(xiàn)模型一致性很高。

    ?

    支持WinRT的異步

    1. 提供WinRT標(biāo)準(zhǔn)的異步方法

    通過create_async方法可以將函數(shù)轉(zhuǎn)為異步函數(shù),即這個(gè)方法是返回IAsyncInfo對(duì)象的。通過這個(gè)方法可以將代碼包裝成WinRT中標(biāo)準(zhǔn)的異步方法供其它語言調(diào)用。被包裝的代碼一般是可調(diào)用對(duì)象,在C++11中一般都使用Lambda表達(dá)式。返回的IAsyncInfo的具體類型(上文介紹的四種之一)是有傳入的參數(shù)決定的。

    create_async的聲明:

    ?
  • template<typename?_Function>

  • __declspec(

  • ???noinline

  • )?auto?create_async(const?_Function&?_Func)?->?decltype(ref?new?details::_AsyncTaskGeneratorThunk<_Function>(_Func));

  • 可以看到為了確定這個(gè)模板方法的返回類型使用了C++11的decltype和位置返回類型等新特性。

    通常情況下,傳入create_async的函數(shù)對(duì)象的方法體是一般的代碼。還以把create_task方法的調(diào)用傳入create_async接收的lambda表達(dá)式的方法體中,create_task返回的concurrency::task也可以配置一系列的then(),最終這些配置都將反應(yīng)給最外部的create_async的包裝。

    下面的代碼就是包裝了最簡(jiǎn)單的過程代碼:

    ?
  • IAsyncOperation<int>^?op2?=?create_async([]()??

  • {??

  • ?????return?0;??

  • });

  • 也可以像上面說的包裝一段create_task的代碼(把C++內(nèi)部的任務(wù)暴露給WinRT接口):

    ?
  • IAsyncOperation<int>^?op3?=?create_async([](){

  • ????return?create_task(KnownFolders::DocumentsLibrary->GetFileAsync("Dictionary.txt")).then([](StorageFile^?file)

  • ????{????????

  • ????????int?wordNum?=?0;

  • ????????//?獲取單詞數(shù)

  • ????????return?wordNum;

  • ????};

  • });

  • 通過create_async的重載也可以輕松的支持取消和進(jìn)度報(bào)告。

    擴(kuò)展的C++使用的異步模式與C# TPL使用的標(biāo)記式取消模型一致,但在使用上還是稍有不同,在介紹這種模式之前,先來說說取消延續(xù)的問題,如下面的代碼:

    ?
  • auto?t1?=?create_task([]()?->?int

  • {

  • ????//取消任務(wù)

  • ????cancel_current_task();

  • });

  • ?
  • auto?t2?=?t1.then([](task<int>?t)

  • {

  • ????try

  • ????{

  • ????????int?n?=?t.get();

  • ????????wcout?<<?L"后續(xù)任務(wù)"?<<?endl;

  • ????}

  • ????catch?(const?task_canceled&?e)

  • ????{

  • ????????

  • ????}

  • });

  • ?
  • auto?t3?=?t1.then([](int?n)

  • {

  • ????wcout?<<?L"后續(xù)任務(wù)"?<<?endl;

  • });

  • 這個(gè)例子中可以看到,我們可以在task內(nèi)部方法中通過cancel_current_task()調(diào)用來取消當(dāng)前的任務(wù)。如果t1被手動(dòng)取消,對(duì)于t1的兩個(gè)后繼任務(wù)t2和t3,t2會(huì)被取消,t3不會(huì)被取消。這是由于t2是基于值延續(xù)的延續(xù),而t3是基于任務(wù)的延續(xù)。

    接下來的示例展示了C++中 的標(biāo)記式取消:

    ?
  • cancellation_token_source?cts;

  • auto?token?=?cts.get_token();

  • ?
  • auto?t?=?create_task([]

  • {

  • ????bool?moreToDo?=?true;

  • ????while?(moreToDo)

  • ????{

  • //是不是的檢查是否取消被設(shè)置

  • ????????if?(is_task_cancellation_requested())

  • ????????{

  • //取消任務(wù)

  • ????????????cancel_current_task();

  • ????????}

  • ????????else?

  • ????????{

  • ????????????moreToDo?=?do_work();

  • ????????}

  • ????}

  • },?token).then([]{

  • //?延續(xù)任務(wù)

  • },token,concurrency::task_continuation_context::use_current);//傳遞取消標(biāo)記,接收取消標(biāo)記的重載還需要延續(xù)上下文的參數(shù)

  • ?
  • //?觸發(fā)取消

  • cts.cancel();

  • ?
  • t.wait();

  • 通過使用cancellation_token,取消也可以傳遞到基于任務(wù)的延續(xù)。

    上面演示的例子cancellation_token是在create_async方法內(nèi)部定義的,更常見的情況在create_async的工作方法參數(shù)中顯示聲明cancellation_token并傳入到工作方法內(nèi),這樣IAsyncXXX上面的Cancel方法被調(diào)用,取消標(biāo)志也會(huì)被自動(dòng)設(shè)置,從而觸發(fā)鏈?zhǔn)降臉?biāo)記性取消。

    說起來很抽象,可以參考下面的代碼:

    ?
  • IAsyncAction^?DoSomething(){

  • return?create_async([](cancellation_token?ct)

  • {

  • auto?t?=?create_task([ct]()

  • {

  • //?do?something

  • });

  • });

  • }

  • 這樣當(dāng)DoSomething返回值(IAsyncAction對(duì)象)的Cancel方法被調(diào)用后,ct被標(biāo)記為取消,任務(wù)t會(huì)在合適的時(shí)間被取消執(zhí)行。

    C++的cancellation_token有一個(gè)更高級(jí)的功能:其上可以設(shè)置回調(diào)函數(shù),當(dāng)cts觸發(fā)取消時(shí),token被標(biāo)記為取消時(shí),會(huì)執(zhí)行這個(gè)回調(diào)函數(shù)的代碼。

    ?
  • cancellation_token_registration?cookie;

  • cookie?=?token.register_callback([&e,?token,?&cookie]()

  • {

  • ????//?記錄task被取消的日志等

  • ?
  • ????//?還可以取消注冊(cè)的回調(diào)

  • ????token.deregister_callback(cookie);

  • });

  • 說完取消,再來看一下進(jìn)度報(bào)告。下面的例子基本是演示進(jìn)度報(bào)告最簡(jiǎn)單的例子。

    ?
  • IAsyncOperationWithProgress<int,?double>^?DoSometingWithProgressAsync(int?input)

  • {

  • ????return?create_async([this,?input](progress_reporter<double>?reporter)?->?int

  • ????{

  • auto?results?=?input;

  • ?
  • reporter.report(1);

  • //?do?something

  • reporter.report(50);

  • //?do?something

  • reporter.report(100.0);

  • ?
  • ????????return?results;

  • ????});

  • }

  • 我們將一個(gè)concurrency::progress_reporter<T>對(duì)象當(dāng)作參數(shù)傳入create_async接收的工作函數(shù)。然后就可以使用reporter的report方法來報(bào)告進(jìn)度。返回的IAsyncOperationWithProgress類型可以使這個(gè)進(jìn)度報(bào)告與WinRT中調(diào)用這個(gè)方法的代碼協(xié)同工作。

    ?

    2. 調(diào)用WinRT標(biāo)準(zhǔn)的異步方法

    說了創(chuàng)建異步方法,再來看看使用C++調(diào)用WinRT的異步方法。由于C++中沒有async/await那樣的異步模式,所以最值得關(guān)心的就是如何,所以當(dāng)一個(gè)任務(wù)完成后需要手動(dòng)傳入剩余的代碼來繼續(xù)后續(xù)任務(wù)的執(zhí)行,這里需要用到task的then方法,首先我們需要把IAsyncInfo轉(zhuǎn)為task。(其實(shí)上面的代碼已經(jīng)演示了這個(gè)用法)

    不同于C#中通過AsTask方法將IAsyncInfo等類型轉(zhuǎn)為Task對(duì)象。C++中是使用create_task的方法(就是上面介紹的那個(gè),不同的重載)來完成這個(gè)工作:

    auto?createFileTadk?=create_task(folder->CreateFileAsync("aa.txt",CreationCollisionOption::ReplaceExisting));

    接著調(diào)用task的then方法設(shè)置后續(xù)執(zhí)行:

    ?
  • createFileTadk.then([this](StorageFile^?storageFileSample)?{??

  • ?????????String^?filename=storageFileSample->Name;

  • ????????});

  • ?

    捕獲異常方面,不涉及WinRT的部分遵循C++的異常捕獲原則,WinRT交互部分,需要保證拋出的異常可以被WinRT識(shí)別處理。

    除了使用ppltask.h中的擴(kuò)展,還可以使用WRL中的AsyncBase模板類來實(shí)現(xiàn)C++對(duì)WiinRT異步的支持。但后者的代碼過于晦澀,就不再介紹了。

    說回來和WinRT交互就好用的語言還是C#,C++可以用于實(shí)現(xiàn)純算法部分,即位于WinRT下方的部分,只需要在必要的時(shí)候通過WinRT公開讓C#可調(diào)用的接口。這樣代碼的編寫效率和執(zhí)行效率都很高。另外C#的應(yīng)用商店程序支持本地編譯也是大勢(shì)所趨,在WinRT之上使用C#或C++/CX區(qū)別不大。

    ?

    C++ 11 線程&并發(fā)&異步

    C++在沉寂多年之后,終于在新版標(biāo)準(zhǔn)中迎來爆發(fā),其中標(biāo)準(zhǔn)內(nèi)置的線程支持就是一個(gè)完全全新的特性。在之前版本的C++中沒有標(biāo)準(zhǔn)的線程庫(kù),實(shí)現(xiàn)跨平臺(tái)的線程操作一般都要借助于第三方的庫(kù)。現(xiàn)在有了C++11,相同的操作線程的代碼可以在不同的編譯器上編譯執(zhí)行從而可以實(shí)現(xiàn)跨平臺(tái)的線程操作。

    C++新標(biāo)準(zhǔn)中的線程,異步等看起來和C#的機(jī)制非常的像,不知道微軟和C++標(biāo)準(zhǔn)委員會(huì)誰“借鑒”的誰。

    下面按線程,并發(fā)中同步支持,異步這樣的順序來逐個(gè)了解下C++新標(biāo)準(zhǔn)中增加的這些特性。介紹方式以C#的等價(jià)機(jī)制做對(duì)比,篇幅原因很多都是一個(gè)綱領(lǐng)作用,介紹一筆帶過,根據(jù)需要大家自行查找相應(yīng)的功能的具體使用方法。

    線程

    C++11標(biāo)準(zhǔn)庫(kù)中引入了std::thread作為抽象線程的類型。其很多操作和.NET中的Thread類似。

    ?C++ 11C#
    ?std::threadThread
    創(chuàng)建constructorconstructor
    插入一個(gè)線程t.join() ?t表示std::thread對(duì)象,下同t.Join() t表示Thread對(duì)象,下同
    分離線程t.detach()
    獲取線程idt.get_id()Thread.CurrentThread.ManagedThreadId
    線程休眠std::this_thread::sleep_for()Thread.Sleep()

    一段簡(jiǎn)單的綜合示例代碼:

    int?main() {std::thread?t1([](int?a){?std::this_thread::sleep_for(std::chrono::seconds(2))?},?3);t1.join();t1.detach();return?0;???? }

    ?

    多線程 - 互斥

    C++11中內(nèi)建了互斥機(jī)制,可以讓多個(gè)線程安全的訪問同一個(gè)變量。幾種機(jī)制總結(jié)如下(可能并非完全一直,但效果上很類似)

    ?C++ 11C#
    原子類型

    atomic_type

    std::atomic<T>

    Interlocked
    內(nèi)存柵欄memory_order_type?MemoryBarrier
    線程本地存儲(chǔ)thread_local

    ThreadStatic

    LocalDataStoreSlot

    ThreadLocal<T>

    互斥

    std::mutex

    std::timed_mutex

    std::recursive_mutex

    std::recursive_timed_mutex

    Mutex
    lock_guard<T>lock
    通知

    condition_variable

    condition_variable_any

    (notify_one/notify_all)

    ManualResetEvent

    AutoResetEvent

    初始化call_once?

    上面介紹的線程或多線程支持都是一些很底層的接口。針對(duì)異步操作C++11還提供了一些高級(jí)接口,其中具有代表性的對(duì)象就是std::future和std::async。

    std::future和C#中的TaskAwaiter比較相似,而std::async作用正如C#中使用async關(guān)鍵字標(biāo)記的異步方法。在C++11中通過std::async將一個(gè)可調(diào)用對(duì)象包裝廠一個(gè)異步方法,這個(gè)方法將返回一個(gè)std::future對(duì)象,通過std::future可以得到異步方法的結(jié)果。

    看一下這段代碼(來自qicosmos老師的博文)就能明白上面所說:

    ?
  • std::future<int>?f1?=?std::async(std::launch::async,?[](){?

  • ????????return?8;??

  • ????});?

  • ?
  • cout<<f1.get()<<endl;

  • 關(guān)于C++11異步方面的特性,強(qiáng)烈推薦qicosmos老師的博文以及他編寫的圖書《深入應(yīng)用C++11:代碼優(yōu)化與工程級(jí)應(yīng)用》。

    ?

    C# 方法調(diào)用方信息

    新版本的C#提供了方便獲取方法調(diào)用者信息的功能,對(duì)于需要調(diào)試以及輸出一些日志的情況很有用。這樣我們不需要像之前那樣在每個(gè)需要記錄日志的地方硬編碼下調(diào)用的方法名,提高了代碼的可讀性。

    提供這個(gè)新功能的是幾個(gè)應(yīng)用于參數(shù)的Attribute:

    • CallerFilePathAttribute 獲得調(diào)用方法所在的源文件地址

    • CallerLineNumberAttribute 被調(diào)用代碼的行號(hào)

    • CallerMemberNameAttribute 調(diào)用方法的名稱

    使用其簡(jiǎn)單只需要聲明一個(gè)參數(shù),然后把這些Attribute加在參數(shù)前面,在函數(shù)中取到的參數(shù)值就是我們想要的結(jié)果。一個(gè)簡(jiǎn)單的例子如下:

    static?void?Caller() {Called(); }static?void?Called([CallerMemberName]?string?memberName?=?"",[CallerFilePath]?string?sourceFilePath?=?"",[CallerLineNumber]?int?sourceLineNumber?=?0) {Console.WriteLine(memberName);Console.WriteLine(sourceFilePath);Console.WriteLine(sourceLineNumber); }

    輸出如下:

    Main

    C:\Users\...\ConsoleApplication1\Program.cs

    31

    還算是簡(jiǎn)單方便,尤其對(duì)于輸出日志來說。

    ?

    C#5.0還對(duì)Lambda捕獲閉包外變量進(jìn)行了一些小優(yōu)化,這個(gè)在之前文章介紹Lambda時(shí)有介紹,這里不再贅述。

    ?

    C++ 調(diào)用方法信息

    在C中就有宏來完成類似的功能。由于C++可以兼容C,所以在C++11之前,一般都用這種C兼容的方式來獲得被調(diào)用方法的信息。新版的C++對(duì)此進(jìn)行了標(biāo)準(zhǔn)化,增加了一個(gè)名為__func__的宏來完成這個(gè)功能。

    需要注意的是和C#中類似功能獲得調(diào)用方法名稱不同,這個(gè)__func__宏得到的是被調(diào)用方法,即__func__所在方法的名稱。個(gè)人感覺C++中__func__更實(shí)用。仍然是一個(gè)簡(jiǎn)單的例子:

    ?
  • void?Called()

  • {

  • ????std::cout?<<?__func__?<<?std::endl;

  • }

  • ?
  • void?Caller()

  • {

  • ????Called();

  • }

  • 調(diào)用Caller()將輸出"Called"。

    C++中實(shí)現(xiàn)這個(gè)宏的方式就是在編譯過程中在每個(gè)方法體的最前面插入如下代碼:

    static?const?char*?__func__?=?"Called";

    了解這個(gè)之后你會(huì)感覺這個(gè)宏沒有那么神秘了。

    除了新被標(biāo)準(zhǔn)化的__func__在大部分C++編譯器中仍然可以使用__LINE__和__FILE__獲取當(dāng)前行號(hào)和所在文件。

    ?

    預(yù)告

    下篇文章將介紹C#6帶來的新特性,C#6中沒有什么重量級(jí)的改進(jìn)(據(jù)說編譯器好像有很大改動(dòng),那個(gè)不了解就不說了,不是一般用戶能仔細(xì)研究的。編譯前端和編譯后端發(fā)展這么多年復(fù)雜程度接近操作系統(tǒng)了),大都是一些語法糖,而且糖的數(shù)量還不少。歡迎繼續(xù)關(guān)注。

    ?

    本文斷斷續(xù)續(xù)寫了很久,中間還出去玩了2周。有什么錯(cuò)誤請(qǐng)指正。

    轉(zhuǎn)載于:https://www.cnblogs.com/lsxqw2004/p/4922374.html

    總結(jié)

    以上是生活随笔為你收集整理的【转】C#与C++的发展历程第一 - 由C#3.0起的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    久久久国产一区二区三区 | 午夜私人影院 | 亚洲黄色av网址 | 免费高清av在线看 | 中文字幕黄色 | 在线观看岛国 | 日韩精品五月天 | 成人va在线观看 | 免费久久精品视频 | 亚洲综合国产精品 | 国产又粗又猛又黄视频 | 婷婷综合 | 国产精品初高中精品久久 | 日本韩国精品在线 | 欧美大片mv免费 | 日日夜夜av | 黄色一级在线视频 | www.伊人网| 97人人超| 久久成人资源 | 亚洲综合五月天 | 黄色大片免费播放 | 91成人免费观看视频 | 亚洲精品久久久蜜桃直播 | 狠狠色综合欧美激情 | 欧美精品久久久久久久久久白贞 | 国产视频1区2区3区 久久夜视频 | 超碰在97| 久久99影院 | 992tv在线| 精品99在线观看 | 亚洲欧洲国产视频 | 久久久国产一区二区三区四区小说 | 午夜精品久久久久久久99热影院 | 欧美另类性 | 精品一区在线看 | 欧美日韩在线视频一区 | 手机色站 | 天天草天天爽 | 久久综合色播五月 | 国产91影视 | 最新av电影网址 | 一级欧美一级日韩 | 日韩乱色精品一区二区 | 日本中文字幕免费观看 | 亚洲精品国偷自产在线91正片 | 亚洲欧美经典 | 激情综合网五月激情 | av激情五月| 国产视频97 | www.91国产 | av黄色一级片 | 欧美天天综合网 | 色综合色综合久久综合频道88 | 日韩久久久久久 | 91自拍视频在线观看 | 日日夜夜婷婷 | 国产精品久久久久久久久久不蜜月 | a极黄色片 | www亚洲国产 | 国产91免费在线观看 | 国产成人久久 | 日本精a在线观看 | 四川bbb搡bbb爽爽视频 | 成年人天堂com | 久草视频国产 | 蜜臀久久99精品久久久久久网站 | 麻豆视频免费播放 | 久久久国产精品电影 | 人人超在线公开视频 | 热99久久精品 | 久草网在线视频 | 中文字幕在线观看1 | 国产一区在线免费观看视频 | 欧美国产日韩一区二区三区 | 国产一级淫片在线观看 | 日本久久免费电影 | 国产视频99 | 一区二区三区电影 | 五月天激情视频 | 亚洲精品在线一区二区三区 | 国产在线观看,日本 | 亚州av成人| 久久情侣偷拍 | 天天综合网天天综合色 | 久久99在线| 在线成人免费 | 免费99| 亚洲激情六月 | 在线免费观看羞羞视频 | 在线免费观看国产 | 亚洲国内精品在线 | 99国产免费网址 | 亚洲国产影院av久久久久 | 中日韩在线 | 天天做日日做天天爽视频免费 | 成人免费视频视频在线观看 免费 | 国产精品99免费看 | 激情av综合| 日韩欧美xxx | 日韩电影在线看 | 国产不卡高清 | 91久久久久久久 | 夜夜躁日日躁 | 中文在线 | 99久久久成人国产精品 | 午夜三级影院 | 国产精品一区二区av日韩在线 | 99视频久久 | 精品视频在线视频 | 成人黄色小视频 | 精品视频资源站 | 亚洲九九爱 | 成年美女黄网站色大片免费看 | 激情视频免费在线观看 | 黄色小说视频在线 | 中文永久字幕 | 免费观看www视频 | 日本中文字幕在线电影 | 亚洲欧洲精品一区二区精品久久久 | 黄色成人免费电影 | 成人小视频免费在线观看 | 日韩欧美一区二区三区在线 | 国产精品视频地址 | 国产精品久久久久久久久久ktv | 射射色| 91伊人久久大香线蕉蜜芽人口 | 中文字幕婷婷 | 久久久久久久久久福利 | 9ⅰ精品久久久久久久久中文字幕 | 精品国产a| 国产91免费在线观看 | 亚洲激情综合网 | 97日日碰人人模人人澡分享吧 | 国产精品一区二区在线免费观看 | 狠狠色综合网站久久久久久久 | 韩国一区二区三区在线观看 | 狠狠综合久久 | 少妇bbbb搡bbbb搡bbbb | 久久国产精品99久久久久 | a在线免费 | 狠狠色狠狠色终合网 | 91日韩在线专区 | 久久午夜视频 | 久久久免费电影 | 久久网站免费 | 丁香花在线视频观看免费 | 五月婷婷六月丁香 | 国产视频欧美视频 | 97免费在线观看视频 | 欧美日韩高清一区二区三区 | 国产精品毛片久久久久久久久久99999999 | 国产精品久久久久久高潮 | 久久国产精品精品国产色婷婷 | 中文字幕91在线 | 免费成人av在线看 | 成人久久久久久久久 | 久久综合九色综合97_ 久久久 | 久久亚洲综合色 | 久久国产精品99久久人人澡 | 青草草在线视频 | 国产老熟 | 天天色综合1 | 成人小视频在线观看免费 | 亚洲91中文字幕无线码三区 | 久久久久久综合 | 中国一级片免费看 | 综合天堂av久久久久久久 | 欧美日韩免费一区 | 久久久久久久99 | 在线观看中文av | 日本资源中文字幕在线 | 国产精品国产三级国产aⅴ9色 | 五月天色网站 | 欧美极品少妇xbxb性爽爽视频 | 国产精品九九久久久久久久 | 国产精品18久久久久久久久 | 九色porny真实丨国产18 | 成人国产综合 | 欧美另类人妖 | av一区二区在线观看中文字幕 | 国产成人精品日本亚洲999 | 国产精品午夜免费福利视频 | 日韩一二三在线 | 欧美日本不卡 | 亚洲 欧美 成人 | 97成人精品视频在线观看 | 免费在线一区二区 | 丁香视频免费观看 | 中文字幕精品在线 | 日韩视频免费在线观看 | 免费观看91视频大全 | 国产精品久久久久久久久毛片 | 天天射综合网视频 | 五月婷婷综合在线观看 | 久久国产精品第一页 | 日韩一区精品 | 欧美一区在线观看视频 | 亚洲成人午夜在线 | 亚洲国产精品成人va在线观看 | 欧美精品一区二区在线播放 | 日日夜夜爱 | 日韩av中文在线 | 麻豆视频免费在线观看 | 91免费观看视频网站 | 伊在线视频 | 亚洲精品ww| 午夜精品久久久久久久久久久久 | 国产精品久久久久久久久久久久冷 | 在线免费色视频 | 狠狠的操狠狠的干 | 97碰碰精品嫩模在线播放 | 国产韩国日本高清视频 | 人人操日日干 | 久久精品毛片基地 | 五月天久久综合 | 天天av资源 | 亚洲精品视频在线观看免费视频 | 日韩在线视频播放 | a√天堂资源 | 精品主播网红福利资源观看 | 国产成人av网址 | av中文字幕在线看 | 欧美一性一交一乱 | 五月天婷婷在线视频 | 天天弄天天操 | 国产精品久久久久免费观看 | 91传媒免费观看 | 一区二区三区电影大全 | 在线观看av免费 | 高清日韩一区二区 | adn—256中文在线观看 | 精品一区免费 | 五月婷婷一级片 | 日韩成人精品一区二区 | 国产三级久久久 | 欧美日韩裸体免费视频 | 国产精品免费不 | 国产剧情一区二区 | 国产亚洲综合精品 | 在线性视频日韩欧美 | 2019天天干夜夜操 | 亚洲撸撸| 亚洲人在线7777777精品 | 精品国产成人在线影院 | 日本一区二区三区免费观看 | 在线观看一区二区视频 | 在线电影 一区 | 九九免费观看视频 | 91一区啪爱嗯打偷拍欧美 | av 一区二区三区 | 在线精品视频在线观看高清 | 五月婷婷狠狠 | 久久在线 | 日韩视频中文字幕 | 成人久久影院 | 国产专区视频 | 在线超碰av | 亚洲综合爱 | 久久天天综合网 | 欧美日韩国产区 | 综合网中文字幕 | 六月婷色| 91视频免费 | a黄色| 成人wwwxxx视频 | 国产成人精品综合久久久久99 | 国产精品美女久久久久aⅴ 干干夜夜 | 西西444www大胆无视频 | 日韩av手机在线看 | 精品亚洲视频在线观看 | 美女视频久久 | av福利在线 | 丁香婷婷在线观看 | 在线观看视频免费播放 | 天天插天天狠天天透 | 亚洲国产视频网站 | 国产精品免费在线观看视频 | 成年人免费在线播放 | 色com网| 伊人夜夜 | 一级特黄aaa大片在线观看 | 久久久影院官网 | 国产精品尤物视频 | 欧美日本中文字幕 | 69国产成人综合久久精品欧美 | 午夜av免费观看 | 免费91在线观看 | 成人毛片a | 日日夜夜天天射 | 亚洲永久精品国产 | 国产视频精品免费 | 久久免费播放视频 | 亚洲麻豆精品 | 日本不卡久久 | 久久精品一区二区三区国产主播 | 国产精品影音先锋 | 久久久免费播放 | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 日韩视频在线一区 | 黄色片免费看 | 最新国产在线 | 亚洲一二三久久 | 亚洲精品美女在线观看播放 | 日日干干 | 久久99热精品这里久久精品 | 免费h精品视频在线播放 | 黄色网址a | 精品国产91亚洲一区二区三区www | 超碰在线最新地址 | 免费久久久久久 | 一级黄色片在线免费观看 | 五月激情婷婷丁香 | 99久高清在线观看视频99精品热在线观看视频 | 日韩精品三区四区 | 五月婷在线播放 | 日韩在线观看网站 | 国产精品久久久久久久久毛片 | 国产欧美最新羞羞视频在线观看 | 久久精品日本啪啪涩涩 | 国产一区二区三区 在线 | 久久蜜臀av | 欧美一级电影在线观看 | 香蕉视频免费在线播放 | 欧美淫aaa免费观看 日韩激情免费视频 | 久久tv视频 | 久久久久久中文字幕 | 国产高清专区 | 日韩有码中文字幕在线 | 99免费在线播放99久久免费 | 91麻豆福利 | 91色在线观看 | 91亚洲狠狠婷婷综合久久久 | 成人手机在线视频 | 成人免费在线观看电影 | 精品免费视频 | 日本精品视频在线观看 | 欧美视屏一区二区 | 日韩一级成人av | 久久国产一区二区 | 在线观看黄av | 最近中文字幕高清字幕在线视频 | 国产色在线观看 | 中文字幕 国产精品 | 国产人在线成免费视频 | 欧美做受xxx| 色操插 | 久久久www成人免费毛片麻豆 | 日韩欧美高清免费 | 天天操夜操 | 日本久久电影 | 免费毛片一区二区三区久久久 | 91日韩在线 | 日日夜夜网| 亚洲最新av在线网址 | www.婷婷com | www色| 亚洲国产成人精品在线 | 日本高清dvd | 在线视频18在线视频4k | 国产九九九九九 | 伊人午夜| 久久深夜 | 超碰日韩在线 | 中文字幕999 | 亚洲一区二区天堂 | 欧美一级大片在线观看 | 97成人资源站 | 四虎影视欧美 | 国产精品18久久久久久不卡孕妇 | 日本三级久久久 | 亚洲永久精品一区 | 日日草av | 在线看黄色av | 在线欧美小视频 | 2018好看的中文在线观看 | 国产精品第52页 | 欧美日韩国产色综合一二三四 | 日韩欧美一区二区三区在线观看 | 国产精品色视频 | 97成人在线视频 | 国产高清av免费在线观看 | 久久精品二区 | 免费国产一区二区视频 | 91av资源在线| 久久中文字幕在线视频 | 操操操人人 | 五月天高清欧美mv | 91精品在线免费观看 | 激情在线网址 | 亚洲精品理论 | 天天干,狠狠干 | 中文字幕在线观看第一页 | 97精产国品一二三产区在线 | 免费看av在线 | 中文字幕在线日 | 美女黄频网站 | 国内视频在线 | 国产成人精品久久亚洲高清不卡 | 人人插人人艹 | 精品毛片久久久久久 | 国产69精品久久99不卡的观看体验 | 六月丁香综合 | 激情综合一区 | 四虎成人网 | 欧美一区二区三区激情视频 | 久草在线一免费新视频 | 亚洲人在线7777777精品 | 操操操av | 成人av资源 | 中文字幕高清 | 中文字幕免费高清在线 | 日韩极品在线 | av丝袜制服 | 射射射av| 蜜臀av性久久久久av蜜臀妖精 | 在线观看国产中文字幕 | 精品国产不卡 | 三级黄色欧美 | 亚洲精品在线观看的 | 国产糖心vlog在线观看 | 精品中文字幕在线观看 | 精品v亚洲v欧美v高清v | 国产二区视频在线观看 | 精品国产一区二区三区蜜臀 | 黄网站色视频免费观看 | 国产精品自产拍在线观看桃花 | 国产免费观看av | 午夜精品视频免费在线观看 | 国产色视频一区二区三区qq号 | 精品毛片久久久久久 | 日韩久久午夜一级啪啪 | 在线看福利av | 亚洲精品乱码久久 | 人人干97| 在线免费看黄色 | 免费的国产精品 | 欧美一区,二区 | 国产美女免费观看 | 午夜91视频| 特级西西www44高清大胆图片 | 99免费精品视频 | 国产麻豆精品久久一二三 | 亚洲最新在线 | 欧美 亚洲 另类 激情 另类 | 色多多视频在线观看 | 99精品免费观看 | 在线视频中文字幕一区 | 在线黄色免费av | 日韩欧美一区二区三区视频 | 成人午夜影院 | 亚洲理论电影 | 一区二区三区在线免费 | 成人综合婷婷国产精品久久免费 | 久久成人综合视频 | 国产视频一二区 | 久久草在线视频国产 | 午夜视频99 | 亚洲国产日韩av | 又黄又刺激视频 | 国产成人精品av在线观 | 五月婷婷导航 | 日韩资源在线观看 | 少妇18xxxx性xxxx片 | 国产成人亚洲精品自产在线 | 中文字幕电影高清在线观看 | 欧美乱熟臀69xxxxxx | 91视频免费看网站 | 久久激情五月丁香伊人 | 日韩精品高清视频 | 精品999在线观看 | 国产日韩一区在线 | 久久字幕| 亚洲一级电影在线观看 | 成人免费视频视频在线观看 免费 | 亚洲成人av片| 色噜噜日韩精品一区二区三区视频 | 91激情视频在线 | 成人性生活大片 | 国产一区免费在线 | 97国产精品久久 | 免费看的黄色网 | 久久国产精品视频观看 | 美女精品网站 | 久久综合久久综合这里只有精品 | 中文字幕成人网 | 国产成人福利在线 | 国产在线国产 | 亚洲四虎在线 | 日韩欧美中文 | 狠狠干美女 | 国内精品久久久久影院一蜜桃 | 国产亚洲欧美精品久久久久久 | 色狠狠一区二区 | 亚洲精品高清一区二区三区四区 | 久久激情婷婷 | 草久久久久久久 | 成人xxxx | 亚洲国产中文在线 | 免费在线观看亚洲视频 | 免费在线观看一区 | 天天操夜夜操国产精品 | 91九色精品女同系列 | 国产一级高清 | 国产精品成人一区二区三区吃奶 | 在线看小早川怜子av | 日韩免费高清 | 欧美孕交vivoestv另类 | 国产亚洲精品美女 | 欧美亚洲专区 | 91精品综合在线观看 | 99在线热播精品免费 | 日日爱网址 | 亚洲精品午夜aaa久久久 | 人人插人人玩 | 九九九视频精品 | 九九九在线 | 欧美日韩久久不卡 | 国产免费成人av | 色91在线视频 | 美女视频久久黄 | 婷婷天天色 | 免费看色的网站 | 国产理论一区二区三区 | 日本99精品 | 国产午夜激情视频 | 国产成人综合精品 | 久久国产精品色av免费看 | 成人在线播放免费观看 | 国产高清专区 | 成人免费av电影 | 国产精品久久久久久久久毛片 | 国产视频在线观看免费 | 精品91在线 | 久久亚洲综合国产精品99麻豆的功能介绍 | 在线视频一二区 | 亚洲欧美日韩一级 | 国产黄a三级三级三级三级三级 | 久久精品波多野结衣 | 涩涩网站在线看 | 国产亚洲一级高清 | 青青河边草免费直播 | 人人添人人澡 | 在线视频欧美精品 | 97国产在线观看 | 亚州日韩中文字幕 | 天堂av色婷婷一区二区三区 | 久久tv| 中文字幕人成不卡一区 | 久久99久久99精品免观看粉嫩 | 国产 日韩 欧美 自拍 | 日韩一区精品 | 亚洲精品乱码白浆高清久久久久久 | 国产网红在线观看 | 高潮毛片无遮挡高清免费 | 国产精品欧美日韩 | 韩国av在线播放 | 91九色视频导航 | 97在线公开视频 | 91九色蝌蚪国产 | av三区在线 | 亚洲一区二区三区毛片 | 91成版人在线观看入口 | 国产精品v欧美精品v日韩 | 国产视频在线免费 | 97色噜噜 | 精品1区2区 | 日本中文在线观看 | 国产在线第三页 | 精品91| 免费h在线观看 | 免费99精品国产自在在线 | 超碰成人免费电影 | 久久国产精品色av免费看 | 久久99精品国产99久久6尤 | 亚洲国产天堂av | 91高清在线 | 五月婷婷一区 | 欧美性做爰猛烈叫床潮 | 国产精品久久久久久久婷婷 | 九九视频网站 | 日本少妇视频 | 毛片美女网站 | 日韩精品视频在线免费观看 | 在线精品观看国产 | 天天天天射 | 在线小视频| 亚洲电影久久 | 国产精品二区在线观看 | 成人免费观看视频网站 | 日本激情视频中文字幕 | 成人国产综合 | 91精品国产一区 | 日韩精品不卡在线观看 | 色综合色综合久久综合频道88 | 亚洲精品国产综合99久久夜夜嗨 | 免费日韩 精品中文字幕视频在线 | 四虎影视欧美 | 手机成人av在线 | 久久成人国产精品免费软件 | 98超碰人人 | 日韩,精品电影 | 97成人资源站 | 高清免费av在线 | 麻豆免费看片 | 国产91精品高清一区二区三区 | www国产亚洲精品久久网站 | 99精品国产高清在线观看 | 国产一级电影网 | a黄色一级| 国产日韩亚洲 | 久色网 | 亚洲高清国产视频 | 精品久久久久久国产偷窥 | 亚洲专区在线播放 | 美女国内精品自产拍在线播放 | se婷婷| 成人a在线观看高清电影 | 激情五月激情综合网 | 99久久久久国产精品免费 | 成人av高清 | 婷婷久久丁香 | 久草在线免费在线观看 | 99色99| 亚洲精品一区二区网址 | 九九热中文字幕 | 97视频在线免费观看 | 亚洲欧美综合 | 黄色官网在线观看 | 91激情| 亚洲韩国一区二区三区 | 日韩成人黄色av | 国产精品mm | 久久久毛片 | 日韩大片在线播放 | 超碰公开在线观看 | 成人在线黄色 | 国产九九精品视频 | 色综合久久中文综合久久牛 | 久久99久久99精品免费看小说 | 亚洲人精品午夜 | 成人福利在线 | 美女网站黄免费 | 久久 亚洲视频 | 国产精品人人做人人爽人人添 | 黄色www在线观看 | 97视频总站 | 欧美国产一区在线 | 免费中文字幕在线观看 | 五月开心激情 | 国产成人一区二区三区在线观看 | 精品uu| 精品久久在线 | 91tv国产成人福利 | 久久看片| 天堂va在线高清一区 | 五月开心六月伊人色婷婷 | 美女免费av| 国产很黄很色的视频 | 国语久久| 日日草夜夜操 | 欧美福利久久 | 91看片看淫黄大片 | 日本中文字幕网址 | 国产黄色高清 | www天天操 | 国产情侣一区 | 久久国产精品一区二区 | 精品99在线视频 | 亚洲人成精品久久久久 | 97色在线 | 免费看污在线观看 | 91久久偷偷做嫩草影院 | 国产破处精品 | 在线看片日韩 | 久久久久久久久久久久久久av | 在线国产日本 | 九九热在线视频免费观看 | 婷婷丁香视频 | 在线观看亚洲视频 | 亚洲欧美视频网站 | 成年人免费看的视频 | 亚洲黄色在线 | 精品久久网 | 日日躁你夜夜躁你av蜜 | 亚洲第一区在线播放 | 96亚洲精品久久久蜜桃 | 亚洲成人黄色在线观看 | 欧美极度另类性三渗透 | 亚洲精品www. | 91久久精品日日躁夜夜躁国产 | 国偷自产视频一区二区久 | 最近日本mv字幕免费观看 | 精品久久久网 | 久久国产三级 | 成人在线免费视频观看 | 国产字幕在线观看 | 国产日韩欧美在线 | 亚洲欧美视屏 | 亚洲精品视频免费在线 | 99热精品国产 | 99久久婷婷国产一区二区三区 | 五月丁香 | 黄色三级网站在线观看 | 在线国产中文 | a级国产乱理论片在线观看 特级毛片在线观看 | 在线观看黄网站 | 久久久久久久久久久免费av | 狠狠躁日日躁狂躁夜夜躁av | 日韩免费中文字幕 | 草久中文字幕 | 免费a v在线 | 国产精品一区二区果冻传媒 | 免费黄在线观看 | 国产小视频在线免费观看 | 久久久久成人精品亚洲国产 | av午夜电影 | 欧美日韩一区二区免费在线观看 | 国产午夜三级 | 涩涩资源网 | 欧美一区二区三区在线观看 | 最近中文字幕第一页 | 在线 视频 一区二区 | www.五月天婷婷 | 天天射天天艹 | 天天操天天操天天操天天操 | 香蕉视频网址 | 国产美女视频网站 | 婷婷久久综合九色综合 | 久久伦理网 | 成人国产精品免费观看 | 国产精品区在线观看 | 免费观看国产视频 | 国产成人精品综合久久久久99 | 国产亚洲成av人片在线观看桃 | 日韩理论在线播放 | 国产日韩精品一区二区三区在线 | 天天做天天爱夜夜爽 | 国产99久久久久久免费看 | 99视频99| 国产视频精品在线 | 日韩精品一区二区免费视频 | 五月天久久久久久 | 色爽网站 | 国产精品视频全国免费观看 | 不卡的av| 91精品老司机久久一区啪 | 黄色aaa毛片 | 999视频网站| 国产精品久久av | 91亚洲精品乱码久久久久久蜜桃 | 久久久99精品免费观看乱色 | 99999精品视频 | 国产高清 不卡 | 日产乱码一二三区别在线 | 91麻豆精品国产91久久久更新时间 | 久久精品精品 | 色资源中文字幕 | 国产一线二线三线性视频 | 99麻豆久久久国产精品免费 | 国产精品精品久久久久久 | 免费观看福利视频 | 欧美色黄| 久久视频免费 | 国产精品久久电影网 | 久久黄色网址 | 人人狠 | 国产在线免费av | www.国产在线 | 精品产品国产在线不卡 | 日日夜夜噜 | 久久视频二区 | 97超碰中文 | 亚洲精品乱码久久久久久写真 | 天天曰夜夜爽 | 久草99 | 深爱婷婷久久综合 | 欧美日韩国产欧美 | av黄色影院 | 日韩网站在线看片你懂的 | 在线 国产 日韩 | 国产午夜精品一区二区三区欧美 | 免费日韩| 亚洲国产网站 | 欧美va天堂在线电影 | 手机看片国产日韩 | 婷婷丁香花 | 成年人视频在线免费播放 | 超碰人人91 | 国产区久久 | 91亚洲影院| 中文字幕韩在线第一页 | 中文字幕在线播放日韩 | 中文字幕欧美日韩va免费视频 | 碰超人人 | 久久视频国产 | 97视频在线观看播放 | 91精选在线观看 | 成人免费一区二区三区在线观看 | 欧美成年黄网站色视频 | 日韩一区二区三免费高清在线观看 | 国产在线精品播放 | 99精品视频一区 | 天天操天天操 | 国产高清视频在线播放 | 欧美性护士 | 黄色一级在线视频 | 综合色在线| 久章草在线| 午夜神马福利 | 亚洲视频 中文字幕 | 国产高清在线视频 | 亚洲视频axxx | 中文字幕在线日本 | 99视频一区二区 | 五月综合久久 | 国产午夜在线观看视频 | 亚洲电影成人 | 国产视频久久久久 | 久草在线久| 亚洲艳情| 亚洲免费av在线播放 | 国产裸体永久免费视频网站 | 中文字幕免费 | 在线免费黄色av | 国产老太婆免费交性大片 | 亚洲国产精品小视频 | 午夜视频福利 | www欧美日韩| 99久久精品免费看国产四区 | 奇米四色影狠狠爱7777 | 国产成人精品综合 | 一区二区视频免费在线观看 | 人人操日日干 | 精品在线视频播放 | 国产一区二区在线免费播放 | 国产精品一区二区62 | 丁香激情网 | 国产精品久久久久久久久大全 | 日韩在线二区 | 美女网站一区 | 美女久久久久久久久久久 | 成人一级 | 国产啊v在线观看 | www.狠狠操.com| 国产精品99久久久久久久久久久久 | 99精品国产99久久久久久97 | 日韩xxxx视频 | 国产在线观看 | 99综合视频 | 久久久精品久久日韩一区综合 | 欧美国产不卡 | 亚洲精品国产品国语在线 | 一区电影 | 久久夜色精品国产欧美乱 | 成人一级电影在线观看 | 色噜噜狠狠狠狠色综合久不 | 亚洲在线高清 | 国产精品久久一卡二卡 | 国产性xxxx | 在线观看网站你懂的 | 色综合国产 | 亚洲九九九在线观看 | 亚洲日本欧美在线 | 久久精品日本啪啪涩涩 | 亚洲精品美女久久久久网站 | 日韩免费观看高清 | 天天摸天天操天天爽 | 欧美美女视频在线观看 | 国产视频在线观看一区 | 男女激情免费网站 | 女人18精品一区二区三区 | 国色天香在线观看 | 久久久影院一区二区三区 | 日本三级中文字幕在线观看 | 免费在线黄 | 亚洲成免费 | 91欧美视频网站 | 首页国产精品 | 欧美精品一区二区免费 | a级片在线播放 | 久久免费电影网 | 日韩精品免费在线播放 | 国产高清在线精品 | 欧美成人按摩 | 欧美 日韩 成人 | 亚洲一区二区三区四区精品 | 中文字幕在线免费观看 | 九九国产视频 | 中文字幕在线观看完整版 | 五月婷久久| 国产+日韩欧美 | 国产98色在线 | 日韩 | 午夜精品久久久久久久99 | 国产一区二区精品 | 国产精品麻豆果冻传媒在线播放 | 西西www4444大胆在线 | 久久久国产精品一区二区三区 | 精品一二三四五区 | 日日草天天干 | 国产一区在线免费观看视频 | 成人中文字幕+乱码+中文字幕 | 中文资源在线观看 | 波多野结衣最新 | 久久久久久久久久久久亚洲 | 久久综合久久88 | 黄色av一级片 | 欧美日韩视频在线播放 | 天天射天天干天天插 | 中文字幕在线看视频国产 | 丁香六月婷婷 | 婷婷丁香花五月天 | 热久久99这里有精品 | 亚洲资源在线网 | 在线观看亚洲精品 | 99热最新网址 | 天干啦夜天干天干在线线 | 日本免费一二三区 | 久久婷婷国产色一区二区三区 | 国产老太婆免费交性大片 | 日本精品一区二区三区在线播放视频 | 中文字幕二区在线观看 | 国产色视频一区二区三区qq号 | 久久精品草 | 爱情影院aqdy鲁丝片二区 | 亚洲黄电影| 米奇狠狠狠888 | 久草免费在线观看 | 最近中文字幕完整高清 | 国产亚洲精品久久久久久网站 | 中文视频在线 | 欧美精品乱码久久久久久按摩 | 国产亚洲视频在线免费观看 | 免费中午字幕无吗 | 五月婷婷深开心 | 五月天亚洲综合 | 91精品国产自产老师啪 | a国产精品| 中文字幕一区二区在线观看 | 久久国产乱 | 亚洲精品在线视频播放 | 亚洲国产精品成人女人久久 | 亚洲精品成人网 | 人人爽人人爽人人片 | 国产成人精品av在线观 | 一本到视频在线观看 | 国产一级黄色av | 中文字幕 国产专区 | 中文在线免费视频 | 欧洲激情在线 | 激情五月看片 | 久久久久久久精 | 91麻豆免费版 | 色在线视频 | 久久激情视频免费观看 | 中文区中文字幕免费看 | 国产成人精品一区二区三区福利 | 日韩成人免费电影 | 国产精品久久久久久吹潮天美传媒 | 午夜精品成人一区二区三区 | 日韩精品中文字幕久久臀 | 美女久久精品 | 在线亚洲午夜片av大片 | 免费观看日韩 | 超碰成人免费电影 | 国产色女| a级成人毛片 | 久久超级碰视频 | 国产免费观看视频 | 国产视频精品免费播放 | 亚洲 欧美变态 另类 综合 | 久久免费精品一区二区三区 | 久久久久久不卡 | 韩国在线一区二区 | 久久这里只有精品视频99 | 99精品在线播放 | 91视频国产免费 | 探花系列在线 | 婷婷新五月| 在线视频 区 | 国产 成人 久久 | 在线亚洲免费视频 | 涩涩网站在线 | 91在线看网站 | 国产一区视频在线 | 日韩毛片在线免费观看 | 欧美一区二区视频97 | 久久成人免费视频 | 亚洲精品乱码久久久久久蜜桃动漫 | 国产精品自产拍在线观看桃花 | 色偷偷88888欧美精品久久 | 国产成人综合图片 | 日本精品视频在线观看 | 色成人亚洲网 | 亚洲电影第一页av | 日本激情视频中文字幕 | 色激情五月 | 欧美日韩高清一区二区三区 |