【问题标题】:More effecient way of calling GetStringAsync multiple times?多次调用 GetStringAsync 的更有效方法?
【发布时间】:2017-03-08 02:45:14
【问题描述】:

我有(我的 url 列表大约有 1000 个 url),我想知道是否有更有效的调用来自同一站点的多个 url(已经更改 ServicePointManager.DefaultConnectionLimit)。

另外,重复使用相同的HttpClient 还是在每次调用时创建一个新的更好,下面只使用一个而不是多个。

using (var client = new HttpClient { Timeout = new TimeSpan(0, 5, 0) })
{
    var tasks = urls.Select(async url =>
    {
        await client.GetStringAsync(url).ContinueWith(response =>
        {
           var resultHtml = response.Result;
           //process the html

        });
    }).ToList();

    Task.WaitAll(tasks.ToArray());
}

正如@cory 建议的那样
这是使用TPL 的修改代码,但是我必须设置MaxDegreeOfParallelism = 100 以达到与基于任务的速度大致相同的速度,下面的代码可以改进吗?

var downloader = new ActionBlock<string>(async url =>
{
    var client = new WebClient();
    var resultHtml = await client.DownloadStringTaskAsync(new Uri(url));


}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 100 });

foreach(var url in urls)
{
    downloader.Post(url);
}
downloader.Complete();
downloader.Completion.Wait();

最终

public void DownloadUrlContents(List<string> urls)
{
    var watch = Stopwatch.StartNew();

    var httpClient = new HttpClient();
    var downloader = new ActionBlock<string>(async url =>
    {
        var data = await httpClient.GetStringAsync(url);
    }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 100 });

    Parallel.ForEach(urls, (url) =>
    {
        downloader.SendAsync(url);
    });
    downloader.Complete();
    downloader.Completion.Wait();

    Console.WriteLine($"{MethodBase.GetCurrentMethod().Name} {watch.Elapsed}");    
}

【问题讨论】:

  • 我建议使用 TPL 数据流来限制运行中的任务数量。在当前实现中,您会发现一件事是 HttpClient 请求实际上可能会超时,即使它们尚未在网络上发送。
  • 如果您的 CPU 上没有很多内核,那么大量的并行性是无用的,您只会遇到线程饥饿。尝试使用await SendAsync而不是Post来释放线程,并且不要阻塞任务,一路使用await。 HttpClient 旨在从不同的线程中使用,所以不要每次都创建一个新的
  • @VMAtm 我的机器上有 6 个内核,我有点困惑,你能告诉我这段代码的样子吗?看来我要回到任务作为我的第一个代码没有?
  • @VMAtm 添加了最终代码,我想我全部捕获了
  • 1. Parallel.ForEach 支持异步。 2.您需要等待SendAsync

标签: c# multithreading task-parallel-library tpl-dataflow


【解决方案1】:

虽然您的代码可以正常工作,但通常的做法是为您的ActionBlock 引入一个缓冲区块。为什么要这样做?第一个原因是任务队列大小,您可以轻松调整队列中的消息数量。第二个原因是将消息添加到缓冲区几乎是即时的,然后由TPL Dataflow'负责处理您的所有项目:

// async method here
public async Task DownloadUrlContents(List<string> urls)
{
    var watch = Stopwatch.StartNew();

    var httpClient = new HttpClient();

    // you may limit the buffer size here
    var buffer = new BufferBlock<string>();
    var downloader = new ActionBlock<string>(async url =>
        {
            var data = await httpClient.GetStringAsync(url);
            // handle data here
        },
        // note processot count usage here
        new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Environment.ProcessorCount });
    // notify TPL Dataflow to send messages from buffer to loader
    buffer.LinkTo(downloader, new DataflowLinkOptions {PropagateCompletion = true});

    foreach (var url in urls)
    {
        // do await here
        await buffer.SendAsync(url);
    }
    // queue is done
    buffer.Complete();

    // now it's safe to wait for completion of the downloader
    await downloader.Completion;

    Console.WriteLine($"{MethodBase.GetCurrentMethod().Name} {watch.Elapsed}");
}

【讨论】:

  • 您对BufferBlock 的使用在这里没有任何作用。目的是什么?
  • @CoryNelson 正如我所说,使用它比限制ActionBlock BoundedCapacilty 属性更方便。在这里,您将存储消息和处理消息的逻辑分开。我认为这样更好。
  • ActionBlock 已经内置了一个缓冲区——在它前面添加另一个缓冲区并不能让事情变得更好。 BufferBlock 的用例非常有限,实际上只针对分支出来的非线性数据流。
【解决方案2】:

基本上,重复使用HttpClient 更好,因为您不必每次发送请求时都进行身份验证,并且可以使用 cookie 保存会话的状态,除非您使用令牌对其进行初始化/cookies 在每个创作中。除此之外,这一切都归结为ServicePoint,您可以在其中设置允许的最大并发连接数。

要以更易于维护的方式进行并行调用,我建议使用AsyncEnumerator NuGet package,它允许您编写如下代码:

using System.Collections.Async;

await uris.ParallelForEachAsync(
    async uri =>
    {
        var html = await httpClient.GetStringAsync(uri, cancellationToken);
        // process HTML
    },
    maxDegreeOfParallelism: 5,
    breakLoopOnException: false,
    cancellationToken: cancellationToken);

【讨论】:

    猜你喜欢
    • 2012-07-08
    • 2018-10-09
    • 1970-01-01
    • 2023-01-27
    • 2017-05-07
    • 1970-01-01
    • 2014-06-01
    • 1970-01-01
    • 2013-10-22
    相关资源
    最近更新 更多