Pipelines可以替换掉那些丑陋的封装(kludge)、变通(workaround)或妥协(compromise)——用一个在框架中设计优雅的专门的解决方案。
敢肯定,下面所覆盖的那些痛点,对于那些工作在"数据协议(data protocol)"层面的人来说,一定非常熟悉。
PIPELINES替代/完善了什么?
首先:现有框架中最接近Pipelines的是什么?很简单,Stream ,Stream API对于那些做过序列化或是数据协议工作的人来说非常熟悉,但是,Stream其实是一个非常模糊的API——它在不同的场景表现地非常不同:
-
一些Stream是只读的,一些是只写的,一些是读/写的
-
一样的实体类型有时候是只读的,而有时是只写的(比如
DeflateStream) -
当一个Stream是读/写时,它像是一个磁带,读写操作全作用于同样的下层数据(
FileStream,MemoryStream) ,而有时它像是两个不同的Stream,读写作用于本质上完全不同的两个Stream(NetworkStream,SslStream)——即duplex stream -
在许多deplex(双工)场景下,很难甚至根本不可能表达“之后没有新数据会到来,但是你应该继续读取数据直到结束“——只有
Close(),而它会将deplex的两部分同时关闭 -
有时Stream会是可探查的(Seekable)并且支持
Position和Length的概念,不过大多数不会 -
由于API随着时间的推移,通常会有多种方法来表达同一种操作——比如,我们可以用Read(同步),BeginRead/EndRead(IAsyncResult模式的异步),或者ReadAsync(async/await模式的异步);在多数情况下,调用代码无从得知到底哪种方法才是推荐的/最佳的API
-
如果你使用任何一种异步API,通常很难清楚分辨它的线程模型是什么;它实质上是同步的吗?如果不是,是哪个线程会回调?它用了同步上下文吗?线程池?IO complection-port线程?
-
并且在最近,有了允许使用
Span<byte>/Memory<byte>替换byte[]的API——再一次的,调用者无法知道哪一种才是”更好的“API -
这种API本质上鼓励复制数据;需要缓冲区?那是将数据复制到了另一块内存中,需要一个尚未处理的数据仓库?同样是复制了数据到另一块内存中
所以即使在开始讨论现实世界中的Stream例子和使用它们所导致的问题之前,很明显Stream API本身已经有了很多问题,所以首先显而易见的是,Pipelines解决了这些混乱
什么是PIPELINES
说起"Pipelines",指的是一组4个关键API,它们实现对一个二进制流解耦、重叠(overlapped)的读写访问,包括缓冲区管理(池化,回收),线程感知,丰富的积压控制,和通过背压达到的溢出保护——所有这些都基于一个围绕非连续内存设计的 API,That's a heck of a word salad——但是不要担心,我会讨论每一个元素来解释我的意思。
从简单的开始:对一个单独的管道进行写入和读取
先准备一个对等的Stream,然后写入一些简单的东西,然后再读取回来——坚持只使用Stream API。将只使用ASCII文本以便不用担心有任何复杂编码的状况,并且读写代码不对下层数据流做任何假设。只是写入数据,并且读取到流的末尾从而消费它。
先用Stream来做这些——熟悉的领域,然后再用Pipelines重新实现它,来看其中的相似和不同之处,在之后,我们将研究在其内部究竟发生了什么,然后就能明白为什么它会吸引我们
也许你会说"啊,我想起来了TextReader/TextWriter",我故意不去使用它们——因为我在这里是在尝试谈论Stream API,这样我们的例子可以扩展到广泛的数据协议和场景
1 using (MemoryStream ms = new MemoryStream()){ 2 // write something 3 WriteSomeData(ms); 4 // rewind - MemoryStream works like a tape 5 ms.Position = 0; 6 // consume it 7 ReadSomeData(ms);}