2017-06-13 38 views
4

我目前正在向Web API提出大量請求。我已嘗試async這個過程,以便我可以在合理的時間內完成此操作,但是我無法控制連接,因此我不會發送超過10個請求/秒的連接。我正在使用信號量進行調節,但我不完全確定它在這種情況下會如何工作,因爲我有一個嵌套循環。使用循環限制併發異步請求

我基本上是得到一個模型列表,每個模型都有一個列表中的天數。我需要爲模型內的每一天提出請求。天數可以從1到約50,99%之間的任何時間,它只會是1。所以我想async每個模型,因爲會有大約3000他們,但我想async日的情況下,有多天需要完成。我需要停留在或低於10個請求/秒,所以我認爲最好的方法是將整個操作的請求限制設置爲10。有沒有一個地方可以讓信號量限制連接整個鏈條?

每個單獨的請求還必須對2不同的數據段提出兩個請求,並且此API現在不支持任何類型的批處理。

我對c#很陌生,對async很新,對WebRequests/HttpClient很新,所以對此有所幫助。我試圖在這裏添加所有相關的代碼。如果你需要其他東西,請告訴我。

public static async Task GetWeatherDataAsync(List<Model> models) 
{ 
    SemaphoreSlim semaphore = new SemaphoreSlim(10); 
    var taskList = new List<Task<ComparisonModel>>(); 

    foreach (var x in models) 
    { 
     await semaphore.WaitAsync(); 
     taskList.Add(CompDaysAsync(x)); 
    } 

    try 
    { 
     await Task.WhenAll(taskList.ToArray()); 
    } 
    catch (Exception e) { } 
    finally 
    { 
     semaphore.Release(); 
    } 
} 

public static async Task<Models> CompDaysAsync(Model model) 
{ 
    var httpClient = new HttpClient(); 
    httpClient.DefaultRequestHeaders.Authorization = new 
       Headers.AuthenticationHeaderValue("Token","xxxxxxxx"); 
    httpClient.Timeout = TimeSpan.FromMinutes(5); 
    var taskList = new List<Task<Models.DateTemp>>(); 

    foreach (var item in model.list) 
    { 
     taskList.Add(WeatherAPI.GetResponseForDayAsync(item, 
      httpClient, Latitude, Longitude)); 
    } 
    httpClient.Dispose(); 
    try 
    { 
     await Task.WhenAll(taskList.ToArray()); 
    } 
    catch (Exception e) { } 

    return model; 
} 

public static async Task<DateTemp> GetResponseForDayAsync(DateTemp date, HttpClient httpClient, decimal? Latitude, decimal? Longitude) 
{ 
    var response = await httpClient.GetStreamAsync(request1); 
    StreamReader myStreamReader = new StreamReader(response); 
    string responseData = myStreamReader.ReadToEnd(); 
    double[] data = new double[2]; 
    if (responseData != "[[null, null]]") 
    { 
     data = Array.ConvertAll(responseData.Replace("[", "").Replace("]", "").Split(','), double.Parse); 
    } 
    else { data = null; }; 

    double precipData = 0; 
    var response2 = await httpClient.GetStreamAsync(request2); 
    StreamReader myStreamReader2 = new StreamReader(response2); 
    string responseData2 = myStreamReader2.ReadToEnd(); 
    if (responseData2 != null && responseData2 != "[null]" && responseData2 != "[0.0]") 
    { 
     precipData = double.Parse(responseData2.Replace("[", "").Replace("]", "")); 
    } 
    date.Precip = precipData; 

    if (data != null) 
    { 
     date.minTemp = data[0]; 
     date.maxTemp = data[1]; 
    } 
    return date; 
} 
+0

我只是在使用'Parallel.ForEach'之前做過類似的事情。使用'ParallelOptions'的重載讓你設置'MaxDegreeOfParallelism',但是你需要先用'Enumerable.SelectMany'將每個模型中的日子變平。 – Biscuits

+0

因此,如果我選擇了很多我的收藏,那麼我會得到一個我所有日子沒有與模型本身關係的大名單,雖然,不是嗎?它是否天生與他們有聯繫,還是我需要做一些特別的事情來確保? – DevDevDev

+0

有一個'SelectMany'的重載,它允許您指定一個結果選擇器來將來自父對象和元素的信息投影到一個新對象中。 'Linq'語法使它更容易工作。請記住,'Parallel.ForEach'讓你以異步的方式運行動作(或任務),你仍然可以在每次迭代中「等待」完成它們。 – Biscuits

回答

1

我認爲你完全不明白SemaphoreSlim做什麼。

  1. 你的信號是一種方法級的基於局部變量,所以GetWeatherDataAsync方法調用將產生10調用您的API,而無需等待其他客戶端。
  2. 此外,你的代碼就會死鎖,如果models.Count > 10,因爲你在等待在每個迭代信號,這些請求被堆疊,併爲11th你的線程將永遠掛,因爲你是不是信號釋放:

    var semaphore = new SemaphoreSlim(10); 
    
    foreach (var item in Enumerable.Range(0, 15)) 
    { 
        // will stop after 9 
        await semaphore.WaitAsync(); 
        Console.WriteLine(item); 
    } 
    

你真正需要做的是移動信號,以實例級(甚至TYPE-水平static關鍵字),並等待它GetWeatherDataAsync,並把Releasefinally塊。

至於Parallel.Foreach - 你不應該在這種情況下使用它,因爲它不知道async方法(這是async/await之前推出),以及你的方法看起來並不像它們是CPU密集型的。

+0

你對使用「Parallel.ForEach」的觀點是錯誤的。框架庫如何在C#語言功能有用之前以某種方式依賴於它? – Biscuits

+0

我的意思是說,並行Foreach不適用於異步方法,因爲它不使用異步lambda表達式,只是在第一次返回後將方法標記爲已完成。 – VMAtm

+1

哦,我明白你的意思了。所以你無法在每次迭代中「等待」。 – Biscuits