【转】C#与C++的发展历程第一 - 由C#3.0起
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# Task | C++ concurrency::task |
| 構(gòu)造 方式1 | constructor | constructor |
| 構(gòu)造 方式2 | Task.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)類型 | TaskStatus | concurrency::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++ 11 | C# |
| ? | std::thread | Thread |
| 創(chuàng)建 | constructor | constructor |
| 插入一個(gè)線程 | t.join() ?t表示std::thread對(duì)象,下同 | t.Join() t表示Thread對(duì)象,下同 |
| 分離線程 | t.detach() | 無 |
| 獲取線程id | t.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++ 11 | C# |
| 原子類型 | 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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【转】微软Azure Functions
- 下一篇: 【转】开源的C# websocket-s