优化火灾和忘记使用async / await和tasks

时间:2014-08-27 06:25:06

标签: c# .net multithreading asynchronous async-await

我有大约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。我的系统没线了吗?我该怎么做才能避免这种情况?

3 个答案:

答案 0 :(得分:3)

您的方法将同时启动所有任务,这可能不是您想要的。由于async操作There is no thread,所以不会涉及任何线程,但可能存在多个并发连接限制。

可能有更好的工具可以执行此操作,但如果您想使用async / await,则可以使用this article中记录的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));  
}

答案 1 :(得分:2)

更好的方法是将TPL Dataflow的{​​{1}}与ActionBlock和一个MaxDegreeOfParallelism一起使用:

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); } 可以是used concurrently for multiple requests,因此仅创建和处理单个实例而不是500万个更好。
  • 同时解雇这么多请求有很多问题:机器的网络堆栈,目标网站,超时等等。与HttpClient对应的ActionBlock个上限(您应根据具体情况对其进行测试和优化)。值得注意的是,TPL可能会在认为合适时选择较低的数字。
  • 如果在MaxDegreeOfParallelism方法或lambda表达式的末尾进行单async次调用,则最好在性能上删除冗余async并返回任务(即{ {1}})
  • async-await会通知return block.Completion;不再接受任何项目,但会完成已有的处理项目。完成后,Complete任务将完成,因此您可以ActionBlock

答案 2 :(得分:0)

我怀疑您遇到传出连接管理,导致无法同时连接到同一个域。在这个广泛的Q + A中给出的答案可能会为您提供一些调查途径。

What is limiting the # of simultaneous connections my ASP.NET application can make to a web service?

就您的代码结构而言,我个人尝试使用动态连接池。你知道你实际上不能同时获得5米连接,所以试图尝试它将无法工作 - 你也可以处理(例如)20个连接的合理和配置限制并在池中使用它们。通过这种方式,您可以调高或调低。

或者您可以调查HTTP Pipelining(我没有使用过),它专门用于您正在进行的工作(批量处理Http请求)。 http://en.wikipedia.org/wiki/HTTP_pipelining