首页 > 解决方案 > 如何将 TPL 数据流 TranformBlock 或 ActionBlock 放在单独的文件中?

问题描述

我想为我的 .NET Core 应用程序使用 TPL 数据流,并按照文档中的示例进行操作。

我不想将所有逻辑都放在一个文件中,而是将每个TransformBlockActionBlock(我还不需要其他的)分开到他们自己的文件中。TransformBlock一个将整数转换为字符串的小例子

class IntToStringTransformer : TransformBlock<int, string>
{
    public IntToStringTransformer() : base(number => number.ToString()) { }
}

ActionBlock和一个将字符串写入控制台的小例子

class StringWriter : ActionBlock<string>
{
    public StringWriter() : base(Console.WriteLine) { }
}

不幸的是,这不起作用,因为块类是密封的。有没有办法可以将这些块组织到他们自己的文件中?

标签: c#.net-coretpl-dataflow

解决方案


数据流步骤/块/goroutines 本质上是功能性的,最好组织成工厂函数的模块,而不是单独的类。TPL DataFlow 管道与 F# 或任何其他语言中的函数调用管道非常相似。实际上,可以将其视为一种 PowerShell 管道,只是它更易于编写。

无需创建类或实现接口即可将新功能添加到该管道,您只需添加它并将输出重定向到下一个功能。

TPL 数据流块已经提供了构造管道的原语,并且只需要一个转换函数。这就是为什么它们被密封,以防止滥用。

组织数据流的自然方式也类似于 F# - 创建具有执行每个作业的函数的库,将它们放入相关函数的模块中。这些函数是无状态的,所以它们可以很容易地进入静态库,就像扩展方法一样。

例如,可能有一个用于执行批量插入或读取数据的数据库相关功能的模块,另一个用于处理导出到各种文件格式的模块,用于调用外部 Web 服务的单独类,另一个用于解析特定消息格式的模块。

一个真实的例子

在过去的 7 年中,我一直在为一家在线旅行社 (OTA) 处理多个复杂的管道。其中一个调用几个 GDS(OTA 和航空公司之间的中介)来检索交易信息 - 机票问题、退款、取消等。下一步检索机票记录,详细的机票信息。最后,记录被插入到数据库中。

GDS 太大而无暇顾及标准,因此它们的“SOAP”Web 服务甚至不符合 SOAP,更不用说遵循 WS-* 标准了。所以每个 GDS 都需要一个单独的类库来调用服务并解析输出。那里还没有数据流,项目已经足够复杂了

将数据写入数据库几乎总是相同的,所以有一个单独的项目,其方法采用例如 anIEnumerable<T>并将其写入数据库SqlBulkCopy

加载新数据是不够的,事情经常出错,所以我需要能够加载已经存储的票信息。

组织

为了保持理智:

  • 每个管道都有自己的文件:
    • 用于加载新数据的每日管道,
    • 用于加载所有存储数据的 Reload 管道
    • “重新运行”管道以使用现有数据再次询问任何丢失的数据。
  • 静态类用于保存工作函数和单独的工厂方法,这些方法根据配置生成 Dataflow 块。例如,CreateLogger(path,level)创建一个ActionBlock<Message>记录特定消息的。
  • 常见的数据流Func<TIn,TOut>扩展方法 - 由于 DataFlow 块遵循相同的基本模式,因此通过组合例如 a和 logger 块很容易创建记录块。或者创建一个LinkTo将坏记录重定向到记录器或数据库的重载。这些很常见,它们可以成为扩展方法。

如果它们在同一个文件中,则很难在不影响另一个管道的情况下编辑一个管道。此外,管道比核心任务要多得多,例如:

  • 日志记录
  • 处理不良记录和部分结果(不能因 10 个错误而停止 100K 导入)
  • 错误处理(与处理不良记录不同)
  • 监控——这个怪物在过去的 15 分钟里在做什么?DOP=10 是否提高了性能?

不要创建父管道类

其中一些步骤是常见的,所以首先,我创建了一个父类,其中的公共步骤被重载或简单地替换为子类。非常糟糕的主意。每个管道都相似但不完全相同,继承意味着修改一个步骤或一个连接可能会破坏一切。大约 1 年后,事情变得难以忍受,所以我将父类分成不同的类。


推荐阅读