在.NET Framework 4.5中,C#編譯器實(shí)現(xiàn)了TAP。任何標(biāo)有async關(guān)鍵字的方法都是異步方法,編譯器會(huì)使用TAP執(zhí)行必要的轉(zhuǎn)換從而異步地實(shí)現(xiàn)方法。這樣的方法應(yīng)該返回Task或者Task<TResult>類型。在后者的案例中,方法體應(yīng)該返回一個(gè)TResult,且編譯器將確保通過返回的Task<TResult>是可利用的。相似地,方法體內(nèi)未經(jīng)處理的異常會(huì)被封送到輸出的task,造成返回的Task以Faulted的狀態(tài)結(jié)束。一個(gè)例外是如果OperationCanceledException(或派生類型)未經(jīng)處理,那么返回的Task會(huì)以Canceled狀態(tài)結(jié)束。
開發(fā)者可以手動(dòng)地實(shí)現(xiàn)TAP,就像編譯器那樣或者更好地控制方法的實(shí)現(xiàn)。編譯器依賴來自System.Threading.Tasks命名空間暴露的公開表面區(qū)域(和建立在System.Threading.Tasks之上的System.Runtime.CompilerServices中支持的類型),還有對(duì)開發(fā)者直接可用的功能。當(dāng)手動(dòng)實(shí)現(xiàn)TAP方法時(shí),開發(fā)者必須保證當(dāng)異步操作完成時(shí),完成返回的Task。
在編譯器生成的實(shí)現(xiàn)中混合核心邏輯的實(shí)現(xiàn),對(duì)于手動(dòng)實(shí)現(xiàn)TAP通常是很有用的。比如這種情況,為了避免方法直接調(diào)用者產(chǎn)生而不是通過Task暴露的異常,如:
public Task<int> MethodAsync(string input){ if (input == null) throw new ArgumentNullException("input"); return MethodAsyncInternal(input);}PRivate async Task<int> MethodAsyncInternal(string input){ … // code that uses await}
參數(shù)應(yīng)該在編譯器生成的異步方法之外改變,這種委托有用的另一種場(chǎng)合是,當(dāng)一個(gè)“快速通道”優(yōu)化可以通過返回一個(gè)緩存的task來實(shí)現(xiàn)的時(shí)候。
計(jì)算受限和I/O受限的異步操作可以通過TAP方法實(shí)現(xiàn)。然而,當(dāng)TAP的實(shí)現(xiàn)從一個(gè)庫公開暴露時(shí),應(yīng)該只提供給包含I/O操作的工作負(fù)荷(它們也可以包含計(jì)算,但不應(yīng)該只包含計(jì)算)。如果一個(gè)方法純粹受計(jì)算限制,它應(yīng)該只通過一個(gè)異步實(shí)現(xiàn)暴露,消費(fèi)者然后就可以為了把該任務(wù)卸載給其他的線程的目的來選擇是否把那個(gè)同步方法的調(diào)用包裝成一個(gè)Task,并且/或者來實(shí)現(xiàn)并行。
Task類最適合表示計(jì)算密集型操作。默認(rèn)地,為了提供有效的執(zhí)行操作,它利用了.Net線程池中特殊的支持,同時(shí)也對(duì)異步計(jì)算何時(shí),何地,如何執(zhí)行提供了大量的控制。
生成計(jì)算受限的tasks有幾種方法。
思考下面的渲染圖片的異步方法。task體可以獲得cancellation token為的是,當(dāng)渲染發(fā)生的時(shí)候,如果一個(gè)撤銷請(qǐng)求到達(dá)后,代碼可能過早退出。而且,如果一個(gè)撤銷請(qǐng)求在渲染開始之前發(fā)生,我們也可以阻止任何的渲染。
public Task<Bitmap> RenderAsync( ImageData data, CancellationToken cancellationToken){ return Task.Run(() => { var bmp = new Bitmap(data.Width, data.Height); for(int y=0; y<data.Height; y++) { cancellationToken.ThrowIfCancellationRequested(); for(int x=0; x<data.Width; x++) { … // render pixel [x,y] into bmp } } return bmp; }, cancellationToken);}
如果下面的條件至少一個(gè)是正確的,計(jì)算受限的tasks會(huì)以一個(gè)Canceled狀態(tài)的結(jié)束:
如果該Task體中有另外一個(gè)未經(jīng)處理的異常,那么該Task就會(huì)以Faulted的狀態(tài)結(jié)束,同時(shí)在該task上等待的任何嘗試或者訪問它的結(jié)果都將導(dǎo)致拋出異常。
使用TaskCompletionSource<TResult>類型創(chuàng)建的Tasks不應(yīng)該直接被全部執(zhí)行的線程返回。TaskCompletionSource<TResult>暴露了一個(gè)返回相關(guān)的Task<TResult>實(shí)例的Task屬性。該task的生命周期通過TaskCompletionSource<TResult>實(shí)例暴露的方法控制,換句話說,這些實(shí)例包括SetResult, SetException, SetCanceled, 和它們的TrySet* 變量。
思考這樣的需求,創(chuàng)建一個(gè)在特定的時(shí)間之后會(huì)完成的task。比如,當(dāng)開發(fā)者在UI場(chǎng)景中想要延遲一個(gè)活動(dòng)一段時(shí)間時(shí),這可能使有用的。.NET中的System.Threading.Timer類已經(jīng)提供了這種能力,在一段特定時(shí)間后異步地調(diào)用一個(gè)委托,并且我們可以使用TaskCompletionSource<TResult>把一個(gè)Task放在timer上,例如:
public static Task<DateTimeOffset> Delay(int millisecondsTimeout){ var tcs = new TaskCompletionSource<DateTimeOffset>(); new Timer(self => { ((IDisposable)self).Dispose(); tcs.TrySetResult(DateTimeOffset.UtcNow); }).Change(millisecondsTimeout, -1); return tcs.Task;}
在.Net 4.5中,Task.Delay()就是為了這個(gè)目的而生的。比如,這樣的一個(gè)方法可以使用到另一個(gè)異步方法的內(nèi)部,以實(shí)現(xiàn)一個(gè)異步的輪訓(xùn)循環(huán):
public static async Task Poll( Uri url, CancellationToken cancellationToken, iprogress<bool> progress){ while(true) { await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); bool success = false; try { await DownloadStringAsync(url); success = true; } catch { /* ignore errors */ } progress.Report(success); }}
沒有TaskCompletionSource<TResult>的非泛型副本。然而,Task<TResult>派生自Task,因而,泛型的TaskCompletionSource<TResult>可以用于那些 I/O受限的方法,它們都利用一個(gè)假的TResult源(Boolean是默認(rèn)選擇,如果開發(fā)者關(guān)心Task向下轉(zhuǎn)型的Task<TResult>的消費(fèi)者,那么可以使用一個(gè)私有的TResult類型)僅僅返回一個(gè)Task。比如,開發(fā)的之前的Delay方法是為了順著產(chǎn)生的Task<DateTimeOffset>返回當(dāng)前的時(shí)間。如果這樣的 一個(gè)結(jié)果值是不必要的,那么該方法可以通過下面的代碼取而代之(注意返回類型的改變和TrySetresult參數(shù)的改變):
public static Task Delay(int millisecondsTimeout){ var tcs = new TaskCompletionSource<bool>(); new Timer(self => { ((IDisposable)self).Dispose(); tcs.TrySetResult(true); }).Change(millisecondsTimeout, -1); return tcs.Task;}
異步方法不是僅僅受限于計(jì)算受限或者I/O受限的操作,而是可以代表這兩者的混合。實(shí)際上,通常情況是不同性質(zhì)的多個(gè)異步操作被組合在一起生成更大的混合操作。比如,思考之前的RenderAsync方法,該方法基于一些輸入的ImageData執(zhí)行一個(gè)計(jì)算密集的操作來渲染一張圖片。該ImageData可能來自于一個(gè)我們異步訪問的Web服務(wù):
public async Task<Bitmap> DownloadDataAndRenderImageAsync( CancellationToken cancellationToken){ var imageData = await DownloadImageDataAsync(cancellationToken); return await RenderAsync(imageData, cancellationToken);}
這個(gè)例子也展示了一個(gè)單獨(dú)的CancellationToken是如何通過多個(gè)異步操作被線程化的。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注