TPL DataFlow不处理所有消息

时间:2018-05-16 20:05:09

标签: c# tpl-dataflow

我正在通过HTTP实施简单的数据加载,遵循上一个问题C# .NET Parallel I/O operation (with throttling)提示,由Throttling asynchronous tasks回答。

我分割加载和反序列化,假设一个可能比其他更慢/更快。另外我想限制下载,但不想限制反序列化。因此,我使用了两个块和一个缓冲区。

不幸的是,我遇到的问题是这个管道有时处理的消息少于消耗的消息(我从目标服务器知道我确实做了n个请求,但最终得到的响应更少了。

我的方法看起来像这样(没有错误处理):

    public async Task<IEnumerable<DummyData>> LoadAsync(IEnumerable<Uri> uris)
    {
        IList<DummyData> result;
        using (var client = new HttpClient())
        {
            var buffer = new BufferBlock<DummyData>();

            var downloader = new TransformBlock<Uri, string>(
                async u => await client.GetStringAsync(u),
                new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = _maxParallelism });

            var deserializer =
                new TransformBlock<string, DummyData>(
                    s => JsonConvert.DeserializeObject<DummyData>(s),
                    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded });

            var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };

            downloader.LinkTo(deserializer, linkOptions);
            deserializer.LinkTo(buffer, linkOptions);

            foreach (Uri uri in uris)
            {
                await downloader.SendAsync(uri);
            }

            downloader.Complete();
            await downloader.Completion;

            buffer.TryReceiveAll(out result);
        }

        return result;
    }

更具体地说,我有100个要加载的URL,但我得到90-99个响应。没有错误&amp;服务器处理100个请求。这是随机发生的,大部分时间代码都能正常运行。

1 个答案:

答案 0 :(得分:2)

您的代码存在三个问题:

  1. 正在等待管道的第一个块(downloader)而不是最后一个(buffer)的完成。

  2. 使用TryReceiveAll方法来检索buffer块的消息。从未链接的块中检索所有消息而不引入竞争条件的正确方法是在嵌套循环中使用方法OutputAvailableAsyncTryReceive。您可以找到示例herehere

  3. HttpClient throws an unexpected TaskCanceledException超时的情况下,TPL Dataflow块会忽略这种类型的异常。这两个不幸的现实的结合意味着,默认情况下,任何超时情况都不会被观察到。要解决此问题,您可以像这样更改代码:

var downloader = new TransformBlock<Uri, string>(async url =>
{
    try
    {
        return await client.GetStringAsync(url);
    }
    catch (OperationCanceledException) { throw new TimeoutException(); }
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = _maxParallelism });

第四个不相关的问题是对MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded块使用deserializer选项。在deserializerdownloader慢的情况下(极不可能),deserializer将开始在ThreadPool上排队越来越多的工作,并保持{{3 }}。这将不利于应用程序的性能和响应能力,或者不利于整个系统的健康。实际上,很少有理由将CPU绑定的块配置为MaxDegreeOfParallelism大于Environment.ProcessorCount