0
我想找出使用Task
和async/await
並行HTTP請求的正確方法。我正在使用HttpClient
類,它已經有用於檢索數據的異步方法。如果我只是在foreach循環中調用它並等待響應,則一次只發送一個請求(這很有意義,因爲在await
期間,控制權將返回到我們的事件循環中,而不是返回到foreach循環的下一次迭代中)。並行HTTP請求使用System.Net.Http.HttpClient
我來包裹HttpClient
看起來這樣
public sealed class RestClient
{
private readonly HttpClient client;
public RestClient(string baseUrl)
{
var baseUri = new Uri(baseUrl);
client = new HttpClient
{
BaseAddress = baseUri
};
}
public async Task<Stream> GetResponseStreamAsync(string uri)
{
var resp = await GetResponseAsync(uri);
return await resp.Content.ReadAsStreamAsync();
}
public async Task<HttpResponseMessage> GetResponseAsync(string uri)
{
var resp = await client.GetAsync(uri);
if (!resp.IsSuccessStatusCode)
{
// ...
}
return resp;
}
public async Task<T> GetResponseObjectAsync<T>(string uri)
{
using (var responseStream = await GetResponseStreamAsync(uri))
using (var sr = new StreamReader(responseStream))
using (var jr = new JsonTextReader(sr))
{
var serializer = new JsonSerializer {NullValueHandling = NullValueHandling.Ignore};
return serializer.Deserialize<T>(jr);
}
}
public async Task<string> GetResponseString(string uri)
{
using (var resp = await GetResponseStreamAsync(uri))
using (var sr = new StreamReader(resp))
{
return sr.ReadToEnd();
}
}
}
由我們的事件循環中調用的代碼是
public async void DoWork(Action<bool> onComplete)
{
try
{
var restClient = new RestClient("https://example.com");
var ids = await restClient.GetResponseObjectAsync<IdListResponse>("/ids").Ids;
Log.Info("Downloading {0:D} items", ids.Count);
using (var fs = new FileStream(@"C:\test.json", FileMode.Create, FileAccess.Write, FileShare.Read))
using (var sw = new StreamWriter(fs))
{
sw.Write("[");
var first = true;
var numCompleted = 0;
foreach (var id in ids)
{
Log.Info("Downloading item {0:D}, completed {1:D}", id, numCompleted);
numCompleted += 1;
try
{
var str = await restClient.GetResponseString($"/info/{id}");
if (!first)
{
sw.Write(",");
}
sw.Write(str);
first = false;
}
catch (HttpException e)
{
if (e.StatusCode == HttpStatusCode.Forbidden)
{
Log.Warn(e.ResponseMessage);
}
else
{
throw;
}
}
}
sw.Write("]");
}
onComplete(true);
}
catch (Exception e)
{
Log.Error(e);
onComplete(false);
}
}
我已經嘗試不同的方法涉及Parallel.ForEach
,Linq.AsParallel
,幷包裹了一把循環的全部內容在Task
中。
因此,您是說因爲HTTP庫的異步調用如何工作,我可以同時啓動所有任務,而無需擔心同時發送數千個請求的垃圾郵件。 –
查看接受的答案在這裏:http://stackoverflow.com/questions/19102966/parallel-foreach-vs-task-run-and-task-whenall –
@AustinWagner默認情況下,是的。 HTTP限制是HTTP規範的一部分,因此在技術上禁用(或放寬)它違反了規範。也就是說,我們生活在不同的時代 - 多個併發請求並不像HTTP最初設計時那樣糟糕。無論如何,如果你希望(顯着)限制速度,那麼你可能也想實現自己的節流 - 否則你只是在浪費一堆內存來處理並行處理,而不是將其轉換爲流 - 假設你當然,並不需要所有的迴應。 – Luaan