在连续调用多个异步任务时,异步代码似乎部分阻塞(使用HttpClient)

und*_*ent 9 c# asynchronous task blocking async-await

我一直在努力学习C#与HttpClient的异步,并遇到了一个我无法弄清楚发生了什么的问题.

我想做的事:

  • 一次发送多个HttpClient请求,并在返回后立即单独处理响应.此过程将永远循环重复(即每隔几秒发送一批新请求).对结果进行的处理不是CPU密集型的,每次响应最多只需几毫秒.
  • 在任何一个实例中,数量是5-50个帖子
  • 不关心他们回来的顺序,但每个请求/响应必须尽可能快地处理而不会阻塞.

问题:

  • 每当我一起发送请求列表时,响应似乎要花费更长的时间,但它们应该大致相同.例如:

    所用时间:67.9609毫秒所用
    时间:89.2413毫秒所用
    时间:100.2345毫秒所用
    时间:117.7308毫秒所用
    时间:127.6843毫秒所用
    时间:132.3066毫秒所用
    时间:157.3057毫秒

什么时候它们应该只有70毫米左右...这对于每一批新产品都会重复; 第一个是快速的,然后每个连续的减速.

我的代码
有3个主要部分.我已经简化了它,我可以专注于问题发生的地方(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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • QueryAgent.cs这初始化一个新的Downloader并包含主异步循环调用函数和它在循环中调用的异步方法.
    我需要尽快发送新的请求,具体取决于最后一组响应的结果(基本上,请求发出的速率可能会发生变化,所以我不能在结束时执行Task.delay() while循环)

    public async Task DownloadLoopAsync() 
    {
        while ( this.isLooping )
        {
            // I put this here to stop it using lots of cpu. 
            // Not sure if it is best practice.
            // advice on how to do this kind of loop would be appreciated.
            await Task.Delay( 5 );  
    
            foreach ( string identifier in this.Identifiers )
            {
                string newRequestString = this.generateRequestString(identifier );
                Task newRequest = RequestNewDataAsync(newRequestString);
            }
        }
    }     
    
    public async Task RequestNewDataAsync(string requestString)
    {
        string responseString = await this.downloader.DownloadData(requestString);
        this.ProcessedData = this.ProcessData(responseString);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • Program.cs这只是一个控制台程序,它在QueryAgent中的异步函数上调用一次Task,然后在末尾调用Console.Readline()以停止程序退出.我也在这里初始化HttpClient.我称之为DownloadLoop()一次,如下:

    QueryAgent myAgent = new QueryAgent(httpClient);
    Task dataLoop = myAgent.DownloadLoopAsync();   
    Console.Readline();
    
    Run Code Online (Sandbox Code Playgroud)

我怀疑它与我在DownloadLoopAsync()中的for循环中调用新任务的方式有关,但是这没有意义,因为我假设每个新任务都将由它自己处理.

任何建议都非常感谢,因为我在砖墙试图按预​​期工作后一直打砖墙.

谢谢.

编辑1
在调用Task循环中乱七八糟.我变了:

foreach(string identifier in this.Identifiers)
{
    string newRequestString = this.generateRequestString(identifier );
    Task newRequest = RequestNewDataAsync(newRequestString);
}
Run Code Online (Sandbox Code Playgroud)

foreach(string identifier in this.Identifiers)
{
    string newRequestString = this.generateRequestString(identifier );
    Task newRequest = RequestNewDataAsync(newRequestString);
    await Task.Delay(1);
}
Run Code Online (Sandbox Code Playgroud)

现在它按预期工作:

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
Run Code Online (Sandbox Code Playgroud)

现在我更加困惑为什么这似乎有效!

编辑2
Downloader.cs中,时序代码如下:

public async Task<string> DownloadData(string requestString)
{
    HttpResponseMessage response;
    Stopwatch s = Stopwatch.StartNew();
    response = await this.httpClient.PostAsync( url, new StringContent( requestString ));
    double timeTaken = s.Elapsed.TotalMilliseconds;
    Console.WriteLine( "Time taken: {0} milliseconds", timeTaken );
    string returnString = await response.Content.ReadAsStringAsync();
    return returnString;
}
Run Code Online (Sandbox Code Playgroud)

虽然等待Task.Delay(1); 似乎"做的伎俩",它不是一个理想的解决方案,也不理解为什么它的工作 - 也许它以某种方式它给CPU时间初始化RequestNewDataAsync任务并防止某种类型的锁定同时发生?我只能猜测...这也意味着它将循环限制为1000Hz,这很快,但也是一个限制,因为Task.Delay()只接受整数值,其中1是最小值.

编辑3
多做一些阅读(我对这一切仍然相对较新!),也许这是使用Threadpool(10到100个短期任务)的最佳选择?但是这会产生过多的(每个新任务1MB?)开销; 或者是那种不同的东西.

编辑4
尝试将Task.Delay(1)放在不同的地方.

  • [发生减速]在Downloader.cs中放置httpClient.PostAsync()之前或之后
  • [发生减速]在RequestNewDataAsync()中等待调用之前或之后放置
  • [没有减速/预期性能]在DownloadLoopAsync()中的任务调用之前或之后放置

shr*_*shr 6

当你这样做时await,后续代码只有在你等待完成之后才会被执行.

要同时发送多个查询,您必须Task为每个查询创建新查询,然后等待所有查询.

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
Run Code Online (Sandbox Code Playgroud)