日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

【转】温故之.NET 异步

發布時間:2023/12/10 asp.net 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】温故之.NET 异步 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉自:https://zhuanlan.zhihu.com/p/38537169

這篇文章包含以下內容

  • 異步基礎
  • 基于任務的異步模式
  • 部分 API 介紹

異步基礎

所謂異步,對于計算密集型的任務,就是以線程為基礎的多任務。而在具體使用中,使用線程池里面的線程還是新建獨立線程,取決于具體的任務量;對于?I/O?密集型任務的異步,是以?Windows?事件為基礎的。

.NET?提供了執行異步操作的三種方式:

  • 異步編程模型 (APM) 模式(也稱?IAsyncResult?模式):在此模式中異步操作需要?Begin?和?End?方法(比如用于異步寫入操作的?BeginWrite?和?EndWrite)。不建議新的開發使用此模式
  • 基于事件的異步模式 (EAP):這種模式需要一個或多個事件、事件處理程序委托類型和?EventArg?派生類型,以便在工作完成時觸發。不建議新的開發使用這種模式
  • 基于任務的異步模式 (TAP):它是在?.NET 4?中引入的。C#?中的?async?和?await?關鍵字為?TAP?提供了語言支持。這是推薦使用方法

由于異步編程模型 (APM) 模式與基于事件的異步模式 (EAP)在新的開發中已經不推薦使用。故在此處我們就不介紹了,以下僅介紹基于任務的異步模式(TAP)

基于任務的異步模式(TAP)

任務是工作的異步抽象,而不是線程的抽象。即當一個方法返回了?Task?或?Task<T>,我們不應該認為它一定創建了一個線程,而是開始了一個任務。這對于我們理解?TAP?是非常重要的。

TAP?以?Task?和?Task<T>?為基礎。它把具體的任務抽象成了統一的使用方式。這樣,不論是計算密集型任務,還是?I/O?密集型任務,我們都可以使用?async?、await?關鍵字來構建更加簡潔易懂的代碼

任務分為?計算密集型任務和?I/O密集型任務任務兩種

  • 計算密集型任務:當我們?await?一個操作時,該操作會通過?Task.Run?方法啟動一個線程來處理相關的工作
    工作量大的任務,通過為?Task.Factory.StartNew?指定?TaskCreateOptions.LongRunning選項 可以使新的任務運行于獨立的線程上,而非使用線程池里面的線程
  • I/O 密集型任務:當我們?await?一個操作時,它將返回 一個?Task?或?Task。
    值得注意的是,這兒并不會啟動一個線程

雖然計算密集型任務和?I/O?密集型任務在使用方式上沒有多大的區別,但其底層實現卻大不相同。

那我們如何區分?I/O?密集型任務和計算密集型任務呢?

比如網絡操作,需要從服務器下載我們所需的資源,它就是屬于?I/O?密集型的操作;比如我們通過排序算法對一個數組排序時,這時的任務就是計算密集型任務。


簡而言之,判斷一個任務是計算型還是?I/O?型,就看它占用的?CPU?資源多,還是?I/O?資源多就可以了。

對于I/O密集型的應用,它們是以?Windows?事件為基礎的,因此不需要新建一個線程或使用線程池里面的線程來執行具體工作。但我們仍然可以使用?async、await?來進行異步處理,這得益于 .Net 為我們提供了一個統一的使用方式:?Task?或?Task<T>

舉個例子,對于?I/O?密集型任務,使用方式如下

// 這是在 .NET 4.5 及以后推薦的網絡請求方式 HttpClient httpClient = new HttpClient(); var result = await httpClient.GetStringAsync("https://www.baidu.com");// 而不是以下這種方式(雖然得到的結果相同,但性能卻不一樣,并且在.NET 4.5及以后都不推薦使用) WebClient webClient = new WebClient(); var resultStr = Task.Run(() => {return webClient.DownloadString("https://www.baidu.com"); });

對于計算密集型應用,使用方式如下

Random random = new Random(); List<int> data = new List<int>(); for (int i = 0; i< 50000000; i++) {data.Add(random.Next(0, 100000)); } // 這兒會啟動一個線程,來執行排序這種計算型任務 await Task.Run(() => {data.Sort(); });

異步方法返回?Task?或?Task<TResult>,具體取決于相應方法返回的是?void?還是類型?TResult。如果返回的是?void,則使用?Task,如果是?TResult,則使用?Task<TResult>

不應該使用?out?或?ref?的方式來返回值,因為這可能產生意料之外的結果。因此,我們應該盡可能的使用?Task<TResult>?中的?TResult?來組合多個返回值

另外,await不能用在返回值為 void 的方法上,否則會有編譯錯誤

針對?TAP?的編碼建議

  • async?與?await?應該搭配使用。即它們要么都出現,要么都不出現
  • 僅在異步方法(即被?async?修飾的方法)中使用?await。否則會有編譯器錯誤
  • 如果一個方法內部,沒有使用?await,則該方法不應該使用?async?來修飾,否則會有編譯器警告
  • 如果一個方法為異步方法(被?async?修飾),則它應該以?Async?結尾
  • 我們應該使用非阻塞的方式來編寫等待任務結果的代碼:
    使用?await、await Task.WhenAny、?await Task.WhenAll、await Task.Delay?去等待后臺任務的結果。
    而不是?Task.Wait?、Task.Result、Task.WaitAny、Task.WaitAll、Thread.Sleep,因為這些方式會阻塞當前線程。
    即如果需要等待或暫停,我們應該使用?.NET 4.5?提供的?await?關鍵字,而不是使用?.NET 4.5?之前的版本提供的方式
  • 如果是計算密集型任務,則應該使用?Task.Run?來執行任務;如果是耗時比較長的任務,則應該使用?Task.Factory.StartNew?并指定?TaskCreateOptions.LongRunning?選項來執行任務
  • 如果是?I/O?密集型任務,不應該使用?Task.Run。
    因為?Task.Run?會在一個單獨的線程中運行(線程池或者新建一個獨立線程),而對于?I/O?任務來說,啟用一個線程意義不大,反而會浪費線程資源

創建任務

要創建一個計算密集型任務,在?.NET 4.5?及以后,可采用?Task.Run?的方式來快速創建;如果需要對任務有更多的控制權,則可以使用?.NET 4.0?提供的?Task.Factory.StartNew?來創建一個任務。
對于?I/O?密集型任務,我們可以通過將?await?作用于對應的?I/O?操作方法上即可

取消任務

在?TAP?中,任務是可以取消的。通過?CancellationTokenSource?來管理。需要支持取消的任務,必須持有?CancellationTokenSource.Token?(令牌),以便該任務可以通過?CancellationTokenSource.Cancel()?的方式來取消。

使用?CancellationTokenSource?來取消任務,有以下優點

  • 可以將令牌傳遞給多個任務,這樣可以同時取消多個任務。類似于一個老師,可以管理多個學生。
  • 可以通過?CancellationTokenSource.Token.Register?來監聽任務的取消。這樣我們可以在任務取消之后做一些其他的工作

任務處理進度

我們可以通過?IProgress<T>?接口監聽進度,如下所示

public Task ReadAsync(byte[] buffer, int offset, int count, IProgress<long> progress)

在?.NET 4.5?提供單個?IProgress<T>?實現:Progress<T>。Progress<T>?類的聲明方式如下:

// Progress<T> 類的聲明 public class Progress<T> : IProgress<T> { public Progress(); public Progress(Action<T> handler); protected virtual void OnReport(T value); public event EventHandler<T> ProgressChanged; }

舉個例子,假設我們需要獲取并顯示下載進度,則可以按以下方式書寫

private async void btnDownload_Click(object sender, RoutedEventArgs e) { btnDownload.IsEnabled = false; try { txtResult.Text = await DownloadStringAsync(txtUrl.Text, new Progress<int>(p => pbDownloadProgress.Value = p)); } finally { btnDownload.IsEnabled = true; } }

部分 API 介紹

Task.WhenAll

此方法可以幫助我們同時等待多個任務,所有任務結束(正常結束、異常結束)后返回

這里需要注意的是,如果單個任務有異常產生,這些異常會合并到?AggregateException?中。我們可以通過?AggregateException.InnerExceptions?來得到異常列表;也可以使用?AggregateException.Handle?來對每個異常進行處理,示例代碼如下

public static async void EmailAsync() {List<string> addrs = new List<string>();IEnumerable<Task> asyncOps = addrs.Select(addr => SendMailAsync(addr));try {await Task.WhenAll(asyncOps);} catch (AggregateException ex) {// 可以通過 InnerExceptions 來得到內部返回的異常var exceptions = ex.InnerExceptions;// 也可以使用 Handle 對每個異常進行處理ex.Handle(innerEx => {// 此處的演示僅僅為了說明 ex.Handle 可以對異常進行單獨處理// 實際項目中不一定會拋出此異常if (innerEx is OperationCanceledException oce) {// 對 OperationCanceledException 進行單獨的處理return true;} else if (innerEx is UnauthorizedAccessException uae) {// 對 UnauthorizedAccessException 進行單獨處理return true;}return false;});} }

但,如果我們需要對每個任務進行更加詳細的管理,則可以使用以下方式來處理

public static async void EmailAsync() {List<string> addrs = new List<string>();IEnumerable<Task> asyncOps = addrs.Select(addr => SendMailAsync(addr));try {await Task.WhenAll(asyncOps);} catch (AggregateException ex) {// 此處可以針對每個任務進行更加具體的管理foreach (Task<string> task in asyncOps) {if (task.IsCanceled) {}else if (task.IsFaulted) {}else if (task.IsCompleted) {}}} }

這樣,就應該基本上足夠應對我們工作中的大部分的異常處理了

Task.WhenAny

與?Task.WhenAll?不同,Task.WhenAny?返回的是已完成的任務(可能只是所有任務中的幾個任務)

舉個例子,比如我們開發了一個圖片類App。我們可能需要在打開這個頁面時,同時下載并展示多張圖片。但我們希望無論是哪一張圖片,只要下載完成,就展示出來,而不是所有的圖片都下載完了之后再展示。示例代碼如下

List<Task<Bitmap>> imageTasks = urls.Select(imgUrl => GetBitmapAsync(imgUrl)).ToList(); // 如果我們需要對圖片做一些處理(比如灰度化),可以使用以下代碼 // List<Task<Bitmap>> imageTasks = urls.Select(imgUrl => GetBitmapAsync(imgUrl).ContinueWith(task => ConvertToGray(task.Result)).ToList(); while(imageTasks.Count > 0) { try { Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);// 移除已經下載完成的任務imageTasks.Remove(imageTask); // 同時將該任務的圖片,在UI上呈現出來Bitmap image = await imageTask; panel.AddImage(image); } catch{} }

Task.Delay

此方法用于暫停當前任務的執行,在指定時間之后繼續運行。

它可以與?Task.WhenAny?和?Task.WhenAll?結合,實現任務的超時,如下

public async void btnDownload_Click(object sender, EventArgs e) { btnDownload.Enabled = false; try { Task<Bitmap> download = GetBitmapAsync(url); // 以下的這行代碼表示,如果在 3s 之內沒有下載完成,則認為超時if (download == await Task.WhenAny(download, Task.Delay(3000))) { Bitmap bmp = await download; pictureBox.Image = bmp; status.Text = "Downloaded"; } else { pictureBox.Image = null; status.Text = "Timed out"; var ignored = download.ContinueWith(t => Trace("Task finally completed"));} } finally { btnDownload.Enabled = true; } }

通過這種方式,也可以監聽使用?Task.WhenAll?時多個任務的超時,如下

Task<Bitmap[]> downloads = Task.WhenAll(from url in urls select GetBitmapAsync(url)); if (downloads == await Task.WhenAny(downloads, Task.Delay(3000))) { foreach(var bmp in downloads) panel.AddImage(bmp); status.Text = "Downloaded"; } else {status.Text = "Timed out"; downloads.ContinueWith(t => Log(t)); }

另外,提供兩個有用的函數,以方便我們在項目中使用

RetryOnFail

定義如下所示

// 如果下載資源失敗后,我們希望重新下載時可以使用此方法 // 我們可以指定失敗之后,間隔多長時間才重試。 // 也可以將 retryWhen 指定為 null,以便在失敗之后立即重試 public static async Task<T> RetryOnFail<T>(Func<Task<T>> function, int maxTries, Func<Task> retryWhen) {for (int i = 0; i < maxTries; i++) {try {return await function().ConfigureAwait(false);} catch {if (i == maxTries - 1) throw;}if (retryWhen != null)await retryWhen().ConfigureAwait(false);}return default(T); }

使用方式如下,這在失敗之后,暫停 1s,然后再重試

string pageContents = await RetryOnFail(() => DownloadStringAsync(url), 3, () => Task.Delay(1000));

或者如下,這將在失敗之后立即重試

string pageContents = await RetryOnFail(() => DownloadStringAsync(url), 3, null);

NeedOnlyOne

定義如下

public static async Task<T> NeedOnlyOne<T>(params Func<CancellationToken, Task<T>>[] functions) {var cts = new CancellationTokenSource();var tasks = functions.Select(func => func(cts.Token));var completed = await Task.WhenAny(tasks).ConfigureAwait(false);cts.Cancel();foreach (var task in tasks) {var ignored = task.ContinueWith(t => Trace.WriteLine(t), TaskContinuationOptions.OnlyOnFaulted);}return await completed; }

對于前面我們提到的下載電影的例子:獲取到速度最快的渠道之后,立即取消其他的任務。現在我們可以這樣做

var line = await NeedOnlyOne(token => DetectSpeedAsync("line_1", movieName, cts.Token),token => DetectSpeedAsync("line_2", movieName, cts.Token),token => DetectSpeedAsync("line_3", movieName, cts.Token));

以上提供的這兩個方法,在實際項目中會非常有用,在需要時可以將它們用起來。當然,通過對?Task?的靈活運用,可以組合出更多方便的方法出來。在具體項目中多多使用即可

關于?Task?的一些基本的用法就介紹到這兒了

至此,本節內容講解完畢。下一篇文章我們將講解?.NET?中的并行編程。
歡迎關注公眾號【嘿嘿的學習日記】,所有的文章,都會在公眾號首發,Thank you~

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的【转】温故之.NET 异步的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。