繁体   English   中英

使用异步/等待和任务优化火灾和遗忘

[英]Optimizing for fire & forget using async/await and tasks

我有大约500万个项目需要更新。 我不太在乎响应(有响应会很好,所以我可以记录下来,但是我不希望这样会浪费我时间。)话虽如此,此代码是否经过优化可按以下方式运行尽可能快? 如果有500万件物品,我是否会有取消任何任务或超时错误的风险? 我每秒收到大约1或2个回复。

var tasks = items.Select(async item =>
{
    await Update(CreateUrl(item));
}).ToList();

if (tasks.Any())
{
    await Task.WhenAll(tasks);
}                

private async Task<HttpResponseMessage> Update(string url)
{
    var client = new HttpClient();
    var response = await client.SendAsync(url).ConfigureAwait(false);    
    //log response.
}

更新:我实际上正在获取TaskCanceledExceptions。 我的系统是否耗尽了线程? 我该怎么做才能避免这种情况?

您的方法将同时启动所有任务,这可能不是您想要的。 不会涉及任何线程,因为async操作没有线程 ,但是并发连接数可能会有所限制。

可能有更好的工具来做到这一点,但如果你想使用异步/等待一个选择是使用斯蒂芬Toub的ForEachAsync在记录这篇文章 它允许您控制要执行的同时操作数量,因此不会超出连接限制。

这里是文章的内容:

public static class Extensions
{
     public static async Task ExecuteInPartition<T>(IEnumerator<T> partition, Func<T, Task> body)
     {
         using (partition)
             while (partition.MoveNext())
                await body(partition.Current);
     }

     public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
     {      
         return Task.WhenAll(
             from partition in Partitioner.Create(source).GetPartitions(dop)
                  select ExecuteInPartition(partition, body));
     }
}

用法:

public async Task UpdateAll()
{
    // Allow for 100 concurrent Updates
    await items.ForEachAsync(100, async t => await Update(t));  
}

更好的方法是将TPL DataflowActionBlockMaxDegreeOfParallelism和单个HttpClient

Task UpdateAll(IEnumerable<Item> items)
{
    var block = new ActionBlock<Item>(
        item => UpdateAsync(CreateUrl(item)), 
        new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 1000});

    foreach (var item in items)
    {
        block.Post(item);
    }

    block.Complete();
    return block.Completion;
}

async Task UpdateAsync(string url)
{
    var response = await _client.SendAsync(url).ConfigureAwait(false);    
    Console.WriteLine(response.StatusCode);
}
  • 单个HttpClient可以同时用于多个请求 ,因此最好只创建和处理一个实例,而不是500万个实例。
  • 同时发出如此多的请求有很多问题:机器的网络堆栈,目标网站,超时等等。 ActionBlock使用MaxDegreeOfParallelism (您应针对具体情况进行测试和优化)来限制该数字。 重要的是要注意,TPL认为合适时可以选择一个较小的数字。
  • 当您在async方法或lambda表达式的末尾进行单个async调用时,最好删除冗余的async-await并仅返回任务(例如return block.Completion; ),以return block.Completion;
  • Complete将通知ActionBlock不接受任何其他项目,但完成处理它已经拥有的项目。 完成后, Completion任务将完成,因此您可以await它。

我怀疑您正在遭受传出连接管理的困扰,这阻止了到同一域的大量同时连接。 这个广泛的问题解答中给出的答案可能会为您提供一些调查的途径。

什么限制了我的ASP.NET应用程序可以与Web服务建立的同时连接数?

就您的代码结构而言,我个人将尝试使用动态连接池。 您知道实际上无法同时获得500万个连接,因此尝试尝试将无法正常工作-您最好处理(例如)20个连接的合理且已配置的限制,并在一个池中使用它们。 这样您可以调高或调低。

或者,您可以研究专门用于您正在执行的工作(编排Http请求)的HTTP Pipelining(我尚未使用)。 http://en.wikipedia.org/wiki/HTTP_pipelining

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM