1

我正在构建一个组件,该组件从给定的 url 下载信息并将其解析为我的业务类。

这必须分两个阶段进行。正在下载的页面包含指向在第二阶段下载的一组其他页面的 URL。

我希望所有这些都尽可能并行,并试图通过使用 TPL 数据流框架来降低整体复杂性。

这是我的(简化的)设置:

在此处输入图像描述

  • 我将 URL 发布到缓冲区块,缓冲区块将它们移动到下载块。
  • 在下载块中下载 HTML。
  • 下载块有两个解析块的条件链接,因此页面类型 A 的 html 被移动到“解析页面 A”,这是一个TransformManyBlock
  • 解析页面 A 生成一组指向类型 B 页面的 URL。
  • 这些将再次发布到下载块。
  • 最后,条件链接将页面类型 B 的 HTML 发布到最后一个块。

我正在重用下载块,因为我想通过设置 MaxDegreeOfParallelization 以这种方式限制与服务器的最大连接数。

如果我可以简单地使用两个单独的下载块,设置会容易得多,但是我将无法以这种方式限制连接数,并且仍然有尽可能多的并行连接。

现在我对这个设置的问题:

如何正确传播完成?当我发布完所有 URL 后,我在缓冲区块上调用Complete() 。但是我不能直接将它传播到下载块,因为从“解析页面 A”块生成的 URL 可能仍然需要它,即使在缓冲区块已将所有 URL 发布到它之后。

但我也将下载块完成与缓冲区块和解析页面 A 块完成结合起来,因为这样解析页面 A 将永远不会完成。

我还考虑过在缓冲区块完成后调用“解析页面 A”的 Complete(),但是下载块中​​可能仍有数据将被“解析块 A”拒绝。

有没有办法摆脱这种循环困境?

还是我完全走错了路,应该以其他方式做?

4

1 回答 1

1

从逻辑上讲,您有一个线性管道,所以我认为这也是您应该在代码中对其建模的方式。这意味着每种类型的页面都有一个单独的下载块。这样,完成将正常工作,但您必须单独处理连接限制。

有两种方法我可以看到如何解决这个问题:

  1. 如果您总是连接到同一台服务器,则可以使用ServicePoints来限制与它的连接数。您可以在程序开始时全局设置该限制:

    ServicePointManager.DefaultConnectionLimit = limit;
    

    或仅针对一台服务器:

    ServicePointManager.FindServicePoint(new Uri("http://myserver.com"))
                       .ConnectionLimit = limit;
    
  2. 如果使用ServicePoints 对您不起作用(因为您没有只有一台服务器,因为它会影响整个应用程序,...),您可以使用类似SemaphoreSlim. 信号量将设置为您想要的限制,并将在两个下载块之间共享。

    MaxDegreeOfParallelism对于每个块都将设置为相同的限制(较高的值不会添加任何内容,较低的值可能效率低下)并且它们的代码可能如下所示:

    try
    {
        await semaphore.WaitAsync();
    
        // perform the download
    }
    finally
    {
        semaphore.Release();
    }
    

    如果您确实经常需要这种限制,您可以创建一个封装此逻辑的辅助类。它的用法可能如下所示:

    var factory = new SharedLimitBlockFactory<Input, Output>(
        limit, input => Download(input));
    var downloadBlock1 = factory.CreateBlock();
    var downloadBlock2 = factory.CreateBlock();
    
于 2013-11-04T13:40:11.377 回答