《C#併發編程經典實例》學習筆記—2.5 等待任意一個任務完成 Task.WhenAny

問題

執行若干個任務,只須要對其中任意一個的完成進行響應。這主要用於:對一個操做進行多種獨立的嘗試,只要一個嘗試完成,任務就算完成。例如,同時向多個 Web 服務詢問股票價格,可是隻關心第一個響應的。算法

文中舉的是向多個Web服務詢問股票價格的例子。
我曾在過往的工做中遇到另外一個不太類似的例子。一個問答項目,在問題詳情頁面,重要的是問題展現和回答展現。在該頁面有相關房型推薦和相似問題推薦等等多個模塊展現。也就是說在請求問題數據以外還須要請求多個接口,按理說這個時候最適合的是使用Task.WhenAll,可是當時情形下由於服務器性能受限致使頁面加載過慢影響用戶訪問,因此其時最快須要解決的是頁面加載過慢的問題,因此這時使用Task.WhenAny或許也算得上是一個應急折中的方案,固然這裏不提緩存等其餘優化方案。api

首先查看官方文檔,瞭解全部重載和返回值:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task.whenany?view=netcore-2.2緩存

Task.WhenAny與Task.WhenAll比較:服務器

  • 相同點:參數都是一批任務
  • 不一樣點:Task.WhenAny返回的是完成的任務。

關於返回值的描述有點不太好理解。結合代碼很容易就能明白。併發

// 返回第一個響應的 URL 的數據長度。
private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB)
{
         var httpClient = new HttpClient();
         // 併發地開始兩個下載任務。
         Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
         Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
         // 等待任意一個任務完成。
         Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
         // 返回從 URL 獲得的數據的長度。
         byte[] data = await completedTask;
         return data.Length;
}

注意Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);,使用await獲取返回結果仍然是一個Task任務,若是Task.WhenAny返回的Task有異常,這行代碼並不會拋出異常,而是在byte[] data = await completedTask; 這裏拋出異常。
在第一個任務完成以後,若是其餘任務沒有被取消,也未曾await,那麼這些任務將被遺棄,被遺棄的任務並非表明任務中止,而是任務繼續執行直到完成,固然這些被遺棄的任務的結果或異常都會被忽略。async

文中提到了另外兩個對WhenAny的使用方法。我試着寫了demo。性能

使用Task.WhenAny實現超時功能

書中給出的思路是其中一個任務是Delay的,這樣返回的第一個任務若是是該Delay任務。文中不推薦該方式,由於沒有取消功能,即其餘超時的任務不能被取消,無疑對計算機資源是一種浪費,對性能也會形成影響。優化

// 返回第一個響應的 URL 的數據長度。
        private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB)
        {
            var httpClient = new HttpClient();
            // 併發地開始兩個下載任務。
            Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
            Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
            Task<byte[]> delayTask = GetDelayTask();
            // 等待任意一個任務完成。
            Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB, delayTask);
            // 返回從 URL 獲得的數據的長度。
            byte[] data = await completedTask;
            if (data.Length == 1 && data[0] == byte.MaxValue)
            {
                Console.WriteLine("超時提醒");
            }
            return data.Length;
        }

       
        // 獲取超時任務
        private static Task<byte[]> GetDelayTask()
        {
            return new Task<byte[]>(() =>
            {
                Task.Delay(1000);
                return new[] { byte.MaxValue };
            });
        }

使用Task.WhenAny處理已完成的任務

書中給出的思路是,列表存放Task,完成一個任務就移除一個已完成的Task。文中不推薦此方法,由於執行時間是 O(N^2),2.6小節有 O(N) 的算法。url

// 處理已完成的任務
        private static async Task ProcessTasksAsync(string urlA, string urlB)
        {
            var httpClient = new HttpClient();
            // 併發地開始兩個下載任務。
            Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
            Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
            var tasks = new List<Task<byte[]>> { downloadTaskA, downloadTaskB };
            while (true)
            {
                // 等待任意一個任務完成。
                Task<byte[]> completedTask = await Task.WhenAny(tasks);
                //移除已完成的任務
                tasks.Remove(completedTask);
                if (!tasks.Any())
                {
                    break;
                }
            }
        }
相關文章
相關標籤/搜索