高效方便的IO库: System.IO.Pipelines
我们在编写网络程序的时候,经常会进行如下操作:
- 申请一个缓冲区
 - 从数据源中读入数据至缓冲区
 - 解析缓冲区的数据
 - 重复第2步
 
表面上看来这是一个很常规而简单的操作,但实际使用过程中往往存在如下痛点:
数据读不全:
可能不能在一次read操作中读入所有需要的数据,因此需要在缓冲区中维护一个游标,记录下次读取操作的起始位置,这个游标带了了不小的复杂度:
- 从缓冲区读数据时,要根据游标计算缓冲区起始写位置,以及剩余空间大小。增加了读数据的复杂度。
 - 解析数据也是复用这个缓冲区的,解析的时候也要判断游标起始位置,剩余空间大小。同时增加了解析数据的复杂度。
 - 解析玩了后还要移动游标,重新标记缓冲区起始位置,再次增加了复杂度。
 
缓冲区容量有限:
由于缓冲区有限,可能申请的缓冲区不够用,需要引入动态缓冲区。这也大幅加大了代码的复杂度。
- 如果每次都申请更大的内存,一方面带来的内存申请释放开销,另一方面需要将原来的数据移动,并更新游标,带来更复杂的逻辑。
 - 如果靠多段的内存组成一个逻辑整理,数据的读写方式都比较复杂。
 - 使用完后的内存要释放,如果需要更高的效率还要维持一个内存池。
 
读和用没有分离
我们的业务本身只关心使用操作,但读和用操作没有分离,复杂的都操作导致用操作也变得复杂,并且严重干扰业务逻辑。
今天介绍微软新推出的一个库:System.IO.Pipelines(需要在Nuget上安装),用于解决这些痛点。它主要包含一个Pipe对象,它有一个Writer属性和Reader属性。
var pipe   = new Pipe();
var writer = pipe.Writer;
var reader = pipe.Reader;
Writer对象
Writer对象用于从数据源读取数据,将数据写入管道中;它对应业务中的"读"操作。
var content = Encoding.Default.GetBytes("hello world");
var data    = new Memory<byte>(content);
var result  = await writer.WriteAsync(data);
另外,它也有一种使用Pipe申请Memory的方式
var buffer = writer.GetMemory(512);
content.CopyTo(buffer);
writer.Advance(content.Length);
var result = await writer.FlushAsync();
Reader对象
Reader对象用于从管道中获取数据源,它对应业务中的"用"操作。
首先获取管道的缓冲区:
var result = await reader.ReadAsync();
var buffer = result.Buffer;
这个Buffer是一个ReadOnlySequence<byte>对象,它是一个相当好的动态内存对象,并且相当高效。它本身由多段Memory<byte>组成,查看Memory段的方法有:
- IsSingleSegment: 判断是否只有一段Memory<byte>
 - First: 获取第一段Memory<byte>
 - GetEnumerator: 获取分段的Memory<byte>
 
它从逻辑上也可以看成一段连续的Memory<byte>,也有类似的方法:
- Length: 整个数据缓冲区长度
 - Slice: 分割缓冲区
 - CopyTo: 将内容复制到Span中
 - ToArray: 将内容复制到byte[]中
 
另外,它还有一个类似游标的位置对象SequencePosition,可以从其Position相关函数中使用,这里就不多介绍了。
这个缓冲区解决了"数据读不够"的问题,一次读取的不够下次可以接着读,不用缓冲区的动态分配,高效的内存管理方式带来了良好的性能,好用的接口是我们能更关注业务。
获取到缓冲区后,就是使用缓冲区的数据
var data = buffer.ToArray();
使用完后,告诉PIPE当前使用了多少数据,下次接着从结束位置后读起
reader.AdvanceTo(buffer.GetPosition(4));
这是一个相当实用的设计,它解决了"读了就得用"的问题,不仅可以将不用的数据下次再使用,还可以实现Peek的操作,只读但不改变游标。
交互
除了"读"和"用"操作外,它们之间还需要一些交互,例如:
- 读过程中数据源不可用,需要停止使用
 - 使用过程中业务结束,需要中止数据源。
 
Reader和Writer都有一个Complete函数,用于通知结束:
reader.Complete();
writer.Complete();
在Writer写入和Reader读取时,会获得一个结果
FlushResult result = await writer.FlushAsync();
ReadResult result = await reader.ReadAsync();
它们都有一个IsComplete属性,可以根据它是否为true判断是否已经结束了读和写的操作。
取消
在写入和读取的时候,也可以传入一个CancellationToken,用于取消相应的操作。
writer.FlushAsync(CancellationToken.None);
reader.ReadAsync(CancellationToken.None);
如果取消成功,对应的Result的IsCanceled则为true(没有验证过)
高效方便的IO库: System.IO.Pipelines的更多相关文章
- 使用Apache IO库操作IO与文件
		
--------------siwuxie095 首先到 Apache官网 下载相关的库文件 Apache官网:http://www.apach ...
 - [APUE]标准IO库(上)
		
一.流和FILE对象 系统IO都是针对文件描述符,当打开一个文件时,即返回一个文件描述符,然后用该文件描述符来进行下面的操作,而对于标准IO库,它们的操作则是围绕流(stream)进行的. 当打开一个 ...
 - 文件IO函数和标准IO库的区别
		
摘自 http://blog.chinaunix.net/uid-26565142-id-3051729.html 1,文件IO函数,在Unix中,有如下5个:open,read,write,lsee ...
 - 命名空间System.IO
		
基本介绍:System.IO 命名空间提供读写文件和数据流的类型.基本文件和目录支持的类型. 原文:http://blog.sina.com.cn/s/blog_48a45b950100erhz.ht ...
 - 标准IO与文件IO 的区别【转】
		
本文转载自:http://blog.sina.com.cn/s/blog_63f31f3401013jrn.html 先来了解下什么是标准IO以及文件IO. 标准IO:标准I/O是ANSI C建立的一 ...
 - 标准IO与文件IO 的区别
		
先来了解下什么是标准IO以及文件IO. 标准IO:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性.标准IO库处理很多细节.例如缓存 ...
 - System.IO.Pipelines: .NET上高性能IO
		
System.IO.Pipelines是一个新的库,旨在简化在.NET中执行高性能IO的过程.它是一个依赖.NET Standard的库,适用于所有.NET实现. Pipelines诞生于.NET C ...
 - System.IO.Pipelines: .NET高性能IO
		
System.IO.Pipelines是一个新的库,旨在简化在.NET中执行高性能IO的过程.它是一个依赖.NET Standard的库,适用于所有.NET实现. Pipelines诞生于.NET C ...
 - System.IO.Pipelines来对消息进行Buffer合并
		
System.IO.Pipelines来对消息进行Buffer合并 https://www.cnblogs.com/smark/p/9927455.html .net core使用Pipelines进 ...
 
随机推荐
- python中的break\return\pass\continue用法
			
continue: def func(): for i in range(1,11): if i % 2 == 0: continue # 作用是当符合上面的if判语句后,就直接跳过之后的语句,也就是 ...
 - app和bootloader跳转 MSP与PSP
			
1.不要把跳转函数放在中断中,如此导致在跳转后的app或者bootloder都是在中断状态,只要你一开启该中断,就可能出现硬件中断了 2.如果你的APP使用了ucos系统,在跳转函数中还需要增加__s ...
 - HeadFirst Ruby 第七章总结 references
			
前言 这一章的内容关于 references,讲了当 Ruby 程序中可能会遇到关于 reference 与 object 之间概念混淆而导致的问题. 导言 本章从一个 astronomer 发现 s ...
 - Python 编程快速上手 第十四章 处理 CSV 文件和 JSON 数据
			
前言 这一章分为两个部分,处理 CSV 格式的数据和处理 JSON 格式个数据. 处理 CSV 理解 csv csv 的每一行代表了电子表格中的每一行,每个逗号分开两个单元格csv 的内容全部为文本, ...
 - LeetCode--342--4的幂
			
问题描述: 给定一个整数 (32 位有符号整数),请编写一个函数来判断它是否是 4 的幂次方. 示例 1: 输入: 16 输出: true 示例 2: 输入: 5 输出: false 进阶:你能不使用 ...
 - 关于Androidstudio无法获取到所有的SDk版本,需要挂国内镜像的问题
			
由于墙的原因 我们在使用AndroidStudio的时候SDK Manager无法获取到所有的版本 需要我们设置下使用国内的镜像 ****首先打开Android SDK Manager 然后按照如图 ...
 - Build Castles(构建城堡)
			
Charlemagne, the King of Frankie, 英文描述 请参考图片中的说明. 中文描述 根据给出的数组确定能够盖多少城堡. 思路和点评 我不能确定我的思路是正确的,也欢迎大家参与 ...
 - TP5中的小知识
			
在TP5中如果想用select 查询后,变成数组,用toArray()这个函数的话,必须在连接数据库中把 数据集返回类型变成 'resultset_type'=>'\think\Collecti ...
 - Linux 权限管理命令
			
第四章(二)权限管理命令 Linux常用命令(二)权限管理命令
 - Raspbian安装Opencv3
			
如果旧版本使用apt-get安装 sudo apt-get remove libopencv sudo apt-get remove opencv 如果旧版本使用编译安装 sudo make unin ...