C# 在一行中调用多个异步任务时,异步代码似乎部分阻塞(使用HttpClient)
我一直在尝试学习C#与HttpClient的异步,但遇到了一个问题,我不知道发生了什么 我想做什么:C# 在一行中调用多个异步任务时,异步代码似乎部分阻塞(使用HttpClient),c#,asynchronous,async-await,task,blocking,C#,Asynchronous,Async Await,Task,Blocking,我一直在尝试学习C#与HttpClient的异步,但遇到了一个问题,我不知道发生了什么 我想做什么: 一次性发送多个HttpClient请求,并在响应返回时进行单独处理。此过程在循环中永远重复(即每隔几秒钟发送一批新的请求)。对结果进行的处理不是CPU密集型的,每个响应最多需要几毫秒 在任何情况下,数量为5-50个职位 不关心他们返回的顺序,但每个请求/响应必须在不阻塞的情况下尽可能快地处理 问题: 每当我一起发送一个请求列表时,响应时间似乎会稍微长一点,但它们应该大致相同。e、 g: 所
- 一次性发送多个HttpClient请求,并在响应返回时进行单独处理。此过程在循环中永远重复(即每隔几秒钟发送一批新的请求)。对结果进行的处理不是CPU密集型的,每个响应最多需要几毫秒李>
- 在任何情况下,数量为5-50个职位
- 不关心他们返回的顺序,但每个请求/响应必须在不阻塞的情况下尽可能快地处理
- 每当我一起发送一个请求列表时,响应时间似乎会稍微长一点,但它们应该大致相同。e、 g:
所用时间:67.9609毫秒
所用时间:89.2413毫秒
所用时间:100.2345毫秒
所用时间:117.7308毫秒
所用时间:127.6843毫秒
所用时间:132.3066毫秒
所用时间:157.3057毫秒
主要有三个部分。我已经尽可能地简化了它,以便集中精力解决问题发生的地方(Downloader.cs是我计时的)
- Downloader.cs包含执行实际http查询的方法:
public async Task<string> DownloadData(string requestString) { HttpResponseMessage response; response = await this.httpClient.PostAsync( url, new StringContent( requestString )); string returnString = await response.Content.ReadAsStringAsync(); return returnString; }
- Program.cs这只是一个控制台程序,在QueryAgent中的异步函数上调用一次任务,然后在最后调用console.Readline()以停止程序退出。我也在这里初始化HttpClient。我调用DownloadLoop()一次,如下所示:
QueryAgent myAgent = new QueryAgent(httpClient); Task dataLoop = myAgent.DownloadLoopAsync(); Console.Readline();
只是在调用任务循环中搞得一团糟。我改变了:
foreach(string identifier in this.Identifiers)
{
string newRequestString = this.generateRequestString(identifier );
Task newRequest = RequestNewDataAsync(newRequestString);
}
到
现在,它可以按预期工作:
Time taken: 71.0088 milliseconds
Time taken: 65.0499 milliseconds
Time taken: 64.0955 milliseconds
Time taken: 64.4291 milliseconds
Time taken: 63.0841 milliseconds
Time taken: 64.9841 milliseconds
Time taken: 63.7261 milliseconds
Time taken: 66.451 milliseconds
Time taken: 62.4589 milliseconds
Time taken: 65.331 milliseconds
Time taken: 63.2761 milliseconds
Time taken: 69.7145 milliseconds
Time taken: 63.1238 milliseconds
现在,我更困惑于为什么这看起来是有效的
编辑2Downloader.cs中的正时代码如下所示:
公共异步任务下载数据(字符串请求字符串)
{
HttpResponseMessage响应;
秒表s=Stopwatch.StartNew();
response=wait this.httpClient.PostAsync(url,newStringContent(requestString));
double timetake=s.eassed.total毫秒;
WriteLine(“所用时间:{0}毫秒”,timetake);
string returnString=await response.Content.ReadAsStringAsync();
返回字符串;
}
虽然等待任务。延迟(1);似乎是在“耍把戏”,这不是一个理想的解决方案,我也不明白它为什么会工作——也许它会给CPU时间初始化RequestNewDataAsync任务,并防止某种类型的并发锁定?我只能猜测。。。这还意味着它将循环限制为1000Hz,这是一个快速的限制,但也是一个限制,因为Task.Delay()只接受整数值,其中1是最小值
编辑3多读一点书(我对这一切还比较陌生!),也许这是使用线程池的最佳选择(10到100个短期任务)?然而,这会产生过多的开销(每个新任务1MB?);或者这又是另一回事了 编辑4
在不同的地方做了更多的实验,延迟(1)
- [减速发生]放置在Downloader.cs中httpClient.PostAsync()之前或之后
- [减速发生]在RequestNewDataAsync()中的等待调用之前或之后放置
- [无减速/预期性能]在DownloadLoopAsync()中的任务调用之前或之后放置
wait
时,后续代码仅在您“等待”的内容完成后执行
要同时发送多个查询,您必须为每个查询创建新的任务
,然后一起等待所有查询
var tasks = new List<Task>();
foreach (string identifier in Identifiers)
{
var newRequestString = generateRequestString(identifier);
var newRequest = RequestNewDataAsync(newRequestString);
tasks.Add(newRequest);
}
Task.WhenAll(tasks); // synchronous wait for all tasks to complete
var tasks=newlist();
foreach(标识符中的字符串标识符)
{
var newRequestString=generateRequestString(标识符);
var newRequest=RequestNewDataAsync(newRequestString);
tasks.Add(newRequest);
}
任务。当所有(任务);//同步等待所有任务完成
最有可能的情况是,您的网络或远程服务器上充满了请求。您的无限循环不会在开始下一批之前等待上一批完成,因此随着时间的推移,您正在以超出其完成速度的速度发布批,从而导致积压
这里有一个修改过的循环,它在开始下一批之前等待一批完成。请注意,它不再等待任务。延迟
,而是等待下载任务的集合
public async Task DownloadLoopAsync()
{
while ( this.isLooping )
{
var tasks = this
.Identifiers
.Select(id => RequestNewDataAsync(this.generateRequestString(id));
await Task.WhenAll(tasks);
}
}
注意,在存储处理后数据的结果时,存在竞争条件。这可能是您的问题,也可能不是:
public async Task RequestNewDataAsync(string requestString)
{
string responseString = await this.downloader.DownloadData(requestString);
// Note: you have a race condition here.
// multiple downloads could complete simultaneously and try to concurrently
// assign to ProcessedData. This may or may not be an issue for you.
this.ProcessedData = this.ProcessData(responseString);
}
此外,我还修改了主程序,以便在用户按键后等待最后一批完成:
QueryAgent myAgent = new QueryAgent(httpClient);
Task dataLoop = myAgent.DownloadLoopAsync();
Console.Readline();
myAgent.isLooping = false;
dataLoop.Wait(); // let the last set of downloads complete
您将请求发送到哪里?您是否可能只是在加载服务器,因此响应较慢?
HttpClient
不能同时使用,它不是线程安全的。您也从不等待来自RequestNewDataAsync
的结果任务,因此您会在较早的请求完成之前发送更多请求,给服务器带来越来越多的负载。您的方法似乎有点不标准。我和
public async Task DownloadLoopAsync()
{
while ( this.isLooping )
{
var tasks = this
.Identifiers
.Select(id => RequestNewDataAsync(this.generateRequestString(id));
await Task.WhenAll(tasks);
}
}
public async Task RequestNewDataAsync(string requestString)
{
string responseString = await this.downloader.DownloadData(requestString);
// Note: you have a race condition here.
// multiple downloads could complete simultaneously and try to concurrently
// assign to ProcessedData. This may or may not be an issue for you.
this.ProcessedData = this.ProcessData(responseString);
}
QueryAgent myAgent = new QueryAgent(httpClient);
Task dataLoop = myAgent.DownloadLoopAsync();
Console.Readline();
myAgent.isLooping = false;
dataLoop.Wait(); // let the last set of downloads complete