c# 高级编程 21章472页 【任务和并行编程】【数据流 TPL Data Flow】

159 阅读2分钟

Parallel类,Task类,Parallel LINQ 为数据并行性提供了很多帮助。

但是它们 不能直接支持数据流的处理不能并行转换数据

因此,需要使用 Task Parallel Library Data Flow (TPL Data Flow)

数据流

  • TPL Data Flow的核心:数据块
  • 数据块 可以作为:
    • 提供数据的源
    • 接收数据的目标
    • 既是源又是目标

使用 动作块 ActionBlock<T>

  • 初始化ActionBlock<T>的实例processInput,构造函数是要执行的动作(方法)
  • 调用ActionBlock<T>的实例方法Post(),参数是 传给 要执行的动作(方法) 的 参数
  • 这里,要执行的动作(方法) 是异步运行的。ActionBlock<T>会使用一个任务来并行执行
        public static void Main(string[] args)
        {
            var processInput = new ActionBlock<string>(s =>
            {
                Console.WriteLine($"user input: {s}");
            });

            bool exit = false;
            while (!exit)
            {
                string input = ReadLine();
                if (string.Compare(input, "exit", ignoreCase: true) == 0)
                {
                    exit = true;
                }
                else
                {
                    processInput.Post(input);
                }
            }
        }

使用 源和目标 数据块

块: 每一个块都实现了IDataflowBlock接口

IDataflowBlock接口:

  • Task的属性Completion
  • Complete(): 调用此方法后,块不再接受任何输入,也不再产生任何输出
  • Fault(): 调用此方法,将块放入失败状态

  • 块 可以
    • : 实现了ISourceBlock接口。
    • 目标: 实现了ITargeBlock接口。例如ActionBlock
    • 又是目标: 同时实现了ISourceBlockITargetBlock。例如BufferBlockTransformBlock

ITargeBlock接口 : IDataflowBlock接口, 因此额外提供:

  • OfferMessage(): 发送一条由块处理的消息
  • 扩展方法 Post(): 比OfferMessage()更方便

ISourceBlock接口 : IDataflowBlock接口, 因此额外提供:

  • 链接到目标块以及处理消息的方法

BufferBlock的示例:

        static void Main()
        {
            Task t1 = Task.Run(() => Producer());
            Task t2 = Task.Run(async () => await ConsumerAsync());
            Task.WaitAll(t1, t2);
        }

        private static BufferBlock<string> s_buffer = new BufferBlock<string>();

        public static void Producer()
        {
            bool exit = false;
            while (!exit)
            {
                string input = Console.ReadLine();
                if (string.Compare(input, "exit", ignoreCase: true) == 0)
                {
                    exit = true;
                }
                else
                {
                    s_buffer.Post(input);
                }
            }
        }

        public static async Task ConsumerAsync()
        {
            while (true)
            {
                string data = await s_buffer.ReceiveAsync();
                Console.WriteLine($"user input: {data}");
            }
        }

输出:

intput1
user input: intput1
input2
user input: input2
input3
user input: input3
input4
user input: input4

将多个块 进行 连接

TransformBlock

  • 即是源又是目标
  • 将第一个泛型参数类型,转换 为第二个泛型参数类型
  • 通过委托来完成转换
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks.Dataflow;

namespace DataFlowSample
{
    class Program
    {
        static void Main()
        {
            var target = SetupPipeline();
            target.Post(".");
            Console.ReadLine();
        }

        public static IEnumerable<string> GetFileNames(string path)
        {
            foreach (var fileName in Directory.EnumerateFiles(path, "*.cs"))
            {
                yield return fileName;
            }
        }

        public static IEnumerable<string> LoadLines(IEnumerable<string> fileNames)
        {
            foreach (var fileName in fileNames)
            {
                using (FileStream stream = File.OpenRead(fileName))
                {
                    var reader = new StreamReader(stream);
                    string line = null;
                    while ((line = reader.ReadLine()) != null)
                    {
                        //WriteLine($"LoadLines {line}");
                        yield return line;
                    }
                }
            }
        }

        public static IEnumerable<string> GetWords(IEnumerable<string> lines)
        {
            foreach (var line in lines)
            {
                string[] words = line.Split(' ', ';', '(', ')', '{', '}', '.', ',');
                foreach (var word in words)
                {
                    if (!string.IsNullOrEmpty(word))
                        yield return word;
                }
            }
        }

        public static ITargetBlock<string> SetupPipeline()
        {
            var fileNamesForPath = new TransformBlock<string, IEnumerable<string>>(
              path => GetFileNames(path));

            var lines = new TransformBlock<IEnumerable<string>, IEnumerable<string>>(
              fileNames => LoadLines(fileNames));

            var words = new TransformBlock<IEnumerable<string>, IEnumerable<string>>(
              lines2 => GetWords(lines2));

            var display = new ActionBlock<IEnumerable<string>>(
              coll =>
              {
                  foreach (var s in coll)
                  {
                      Console.WriteLine(s);
                  }
              });

       
            fileNamesForPath.LinkTo(lines);
            lines.LinkTo(words);
            words.LinkTo(display);
            return fileNamesForPath;
        }
    }
}

TPL Data Flow 的其他功能

  • BroadcastBlock: 向多个目标传递输入源
  • JoinBlock: 多个源连接到一个目标
  • BatchBlock: 将输入作为数组进行批处理

DataflowBlockOptions: 配置块

  • 一个 任务 中可以处理的最大项数
  • 传递 CancellationToken, 来取消管道

链接技术:

  • 对消息进行筛选
  • 只传递满足指定条件的消息