针对火灾进行优化&;忘记使用async/await和tasks

本文关键字:async await tasks 忘记 火灾 优化 amp | 更新日期: 2023-09-27 18:19:37

我有大约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/await和tasks

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

可能有更好的工具可以做到这一点,但如果您想使用async/await,一个选项是使用Stephen 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万个实例
  • 同时发出这么多请求会有很多问题:机器的网络堆栈、目标网站、超时等等。ActionBlockMaxDegreeOfParallelism(您应该针对您的特定情况对其进行测试和优化)来限制该数字。需要注意的是,当TPL认为合适时,它可能会选择一个较低的数字
  • 当在async方法或lambda表达式的末尾有一个async调用时,为了提高性能,最好删除冗余的async-await,只返回任务(即return block.Completion;
  • Complete将通知ActionBlock不再接受任何项目,但完成对其已有项目的处理。当它完成时,Completion任务将完成,因此您可以await

我怀疑您正遭受传出连接管理的困扰,无法同时连接到同一域。在这个广泛的问答中给出的答案可能会给你一些调查的途径。

是什么限制了我的ASP.NET应用程序可以同时连接到web服务的次数?

就您的代码结构而言,我个人会尝试使用动态连接池。你知道你实际上不可能同时获得5米的连接,所以尝试尝试它只会失败——你还可以处理(例如)20个连接的合理配置限制,并在池中使用它们。这样你就可以调高或调低音量。

或者,您可以研究HTTP管道化(我没有使用过),它专门用于您正在做的工作(批量处理HTTP请求)。http://en.wikipedia.org/wiki/HTTP_pipelining