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
- 是源又是目标: 同时实现了
ISourceBlock
和ITargetBlock
。例如BufferBlock
,TransformBlock
- 是源: 实现了
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
, 来取消管道
链接技术:
- 对消息进行筛选
- 只传递满足指定条件的消息