如何将TPL数据流TranformBlock或ActionBlock放在单独的文件中?

问题描述

我想为我的.NET Core应用程序使用TPL数据流,并遵循the example from the docs.

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

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

一个小的ActionBlock示例向控制台写入字符串

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

不幸的是,这是行不通的,因为块类是密封的。有什么办法可以将这些块组织到自己的文件中?

解决方法

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

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

TPL Dataflow块提供了原语,已经可以构造管道,并且仅需要转换功能。这就是为什么要密封它们以防止滥用的原因。

组织数据流的自然方式也与F#类似-使用执行所有工作的功能创建库,并将其放入相关功能的模块中。这些函数是无状态的,因此可以像扩展方法一样轻松地进入静态库。

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

真实示例

在过去的7年中,我正在为在线旅行社(OTA)处理多个复杂的流程。其中之一调用多个GDS(OTA与航空公司之间的中介)以检索交易信息-机票发行,退款,取消等。下一步将检索机票记录,详细的机票信息。最后,将记录插入数据库。

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

将数据写入数据库几乎总是一样,因此有一个单独的项目,其方法采用例如IEnumerable<T>并使用SqlBulkCopy写入数据库。

尽管仅加载新数据还不够,但是事情经常出错,因此我需要能够加载已经存储的票证信息。

组织

保持理智:

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

如果它们在同一个文件中,那么很难在不影响另一个管道的情况下编辑一个管道。此外,除了核心任务外,管道还有很多其他内容,例如:

  • 记录
  • 处理不良记录和部分结果(无法停止10个错误的100K导入)
  • 错误处理(与处理不良记录不同)
  • 监视-这个怪物在过去15分钟内在做什么? DOP = 10完全提高了性能吗?

不创建父管道类

其中一些步骤很常见,因此,首先,我创建了一个父类,该类具有重载的通用步骤,或者只是替换为子类。 非常糟糕的想法。每个管道都相似但不完全相同,继承意味着修改一个步骤或一个连接可能会破坏所有内容。大约一年后,事情变得难以忍受,所以我将父班分为几个班。

,

正如@Panagiotis解释的那样,我认为您必须将OOP Mindset放在一边。 DataFlow所具有的是构建模块,您可以对其进行配置以执行所需的内容。我将尝试创建一个我的意思的小例子:

// Interface and impl. are in separate files. Actually,they could 
// even be in a different project ...
public interface IMyComplicatedTransform
{
     Task<string> TransformFunction(int input);
}

public class MyComplicatedTransform : IMyComplicatedTransform
{
     public Task<string> IMyComplicatedTransform.TransformFunction(int input)
     {
         // Some complex logic
     }
}

class DataFlowUsingClass{

     private readonly IMyComplicatedTransform myTransformer;
     private readonly TransformBlock<int,string> myTransform;
     // ... some more blocks ...

     public DataFlowUsingClass()
     {
          myTransformer = new MyComplicatedTransform(); // maybe use ctor injection?
          CreatePipeline();
     }

     private void CreatePipeline()
     {
          // create blocks
          myTransform = new TransformBlock<int,string>(myTransformer.TransformFunction);
          // ... init some more blocks

          // TODO link blocks
     }
}

我认为这与您要寻找的最接近。

最终得到的是一组可以独立测试的接口和实现。客户端基本上可以归结为“ gluecode”。

编辑:正如@Panagiotis正确指出的那样,这些接口甚至是多余的。你可以没有。