C#中的高效IO庫(kù)System.IO.Pipelines
我們?cè)诰帉?xiě)網(wǎng)絡(luò)程序的時(shí)候,經(jīng)常會(huì)進(jìn)行如下操作:
申請(qǐng)一個(gè)緩沖區(qū)
從數(shù)據(jù)源中讀入數(shù)據(jù)至緩沖區(qū)
解析緩沖區(qū)的數(shù)據(jù)
重復(fù)第2步
表面上看來(lái)這是一個(gè)很常規(guī)而簡(jiǎn)單的操作,但實(shí)際使用過(guò)程中往往存在如下痛點(diǎn):
數(shù)據(jù)讀不全:
可能不能在一次read操作中讀入所有需要的數(shù)據(jù),因此需要在緩沖區(qū)中維護(hù)一個(gè)游標(biāo),記錄下次讀取操作的起始位置,這個(gè)游標(biāo)帶了了不小的復(fù)雜度:
從緩沖區(qū)讀數(shù)據(jù)時(shí),要根據(jù)游標(biāo)計(jì)算緩沖區(qū)起始寫(xiě)位置,以及剩余空間大小。增加了讀數(shù)據(jù)的復(fù)雜度。
解析數(shù)據(jù)也是復(fù)用這個(gè)緩沖區(qū)的,解析的時(shí)候也要判斷游標(biāo)起始位置,剩余空間大小。同時(shí)增加了解析數(shù)據(jù)的復(fù)雜度。
解析玩了后還要移動(dòng)游標(biāo),重新標(biāo)記緩沖區(qū)起始位置,再次增加了復(fù)雜度。
緩沖區(qū)容量有限:
由于緩沖區(qū)有限,可能申請(qǐng)的緩沖區(qū)不夠用,需要引入動(dòng)態(tài)緩沖區(qū)。這也大幅加大了代碼的復(fù)雜度。
如果每次都申請(qǐng)更大的內(nèi)存,一方面帶來(lái)的內(nèi)存申請(qǐng)釋放開(kāi)銷(xiāo),另一方面需要將原來(lái)的數(shù)據(jù)移動(dòng),并更新游標(biāo),帶來(lái)更復(fù)雜的邏輯。
如果靠多段的內(nèi)存組成一個(gè)邏輯整理,數(shù)據(jù)的讀寫(xiě)方式都比較復(fù)雜。
使用完后的內(nèi)存要釋放,如果需要更高的效率還要維持一個(gè)內(nèi)存池。
讀和用沒(méi)有分離
我們的業(yè)務(wù)本身只關(guān)心使用操作,但讀和用操作沒(méi)有分離,復(fù)雜的都操作導(dǎo)致用操作也變得復(fù)雜,并且嚴(yán)重干擾業(yè)務(wù)邏輯。
今天介紹微軟新推出的一個(gè)庫(kù):System.IO.Pipelines(需要在Nuget上安裝),用于解決這些痛點(diǎn)。它主要包含一個(gè)Pipe對(duì)象,它有一個(gè)Writer屬性和Reader屬性。
var pipe = new Pipe(); var writer = pipe.Writer; var reader = pipe.Reader;
Writer對(duì)象
Writer對(duì)象用于從數(shù)據(jù)源讀取數(shù)據(jù),將數(shù)據(jù)寫(xiě)入管道中;它對(duì)應(yīng)業(yè)務(wù)中的"讀"操作。
var?content?=?Encoding.Default.GetBytes("hello?world"); var?data????=?new?Memory<byte>(content); var?result??=?await?writer.WriteAsync(data);
另外,它也有一種使用Pipe申請(qǐng)Memory的方式
var?buffer?=?writer.GetMemory(512); content.CopyTo(buffer); writer.Advance(content.Length); var?result?=?await?writer.FlushAsync();
Reader對(duì)象
Reader對(duì)象用于從管道中獲取數(shù)據(jù)源,它對(duì)應(yīng)業(yè)務(wù)中的"用"操作。
首先獲取管道的緩沖區(qū):
var?result?=?await?reader.ReadAsync(); var?buffer?=?result.Buffer;
這個(gè)Buffer是一個(gè)ReadOnlySequence<byte>對(duì)象,它是一個(gè)相當(dāng)好的動(dòng)態(tài)內(nèi)存對(duì)象,并且相當(dāng)高效。它本身由多段Memory<byte>組成,查看Memory段的方法有:
IsSingleSegment: 判斷是否只有一段Memory<byte>
First: 獲取第一段Memory<byte>
GetEnumerator: 獲取分段的Memory<byte>
它從邏輯上也可以看成一段連續(xù)的Memory<byte>,也有類(lèi)似的方法:
Length: 整個(gè)數(shù)據(jù)緩沖區(qū)長(zhǎng)度
Slice: 分割緩沖區(qū)
CopyTo: 將內(nèi)容復(fù)制到Span中
ToArray: 將內(nèi)容復(fù)制到byte[]中
另外,它還有一個(gè)類(lèi)似游標(biāo)的位置對(duì)象SequencePosition,可以從其Position相關(guān)函數(shù)中使用,這里就不多介紹了。
這個(gè)緩沖區(qū)解決了"數(shù)據(jù)讀不夠"的問(wèn)題,一次讀取的不夠下次可以接著讀,不用緩沖區(qū)的動(dòng)態(tài)分配,高效的內(nèi)存管理方式帶來(lái)了良好的性能,好用的接口是我們能更關(guān)注業(yè)務(wù)。
獲取到緩沖區(qū)后,就是使用緩沖區(qū)的數(shù)據(jù)
var?data?=?buffer.ToArray();
使用完后,告訴PIPE當(dāng)前使用了多少數(shù)據(jù),下次接著從結(jié)束位置后讀起
reader.AdvanceTo(buffer.GetPosition(4));
這是一個(gè)相當(dāng)實(shí)用的設(shè)計(jì),它解決了"讀了就得用"的問(wèn)題,不僅可以將不用的數(shù)據(jù)下次再使用,還可以實(shí)現(xiàn)Peek的操作,只讀但不改變游標(biāo)。
交互
除了"讀"和"用"操作外,它們之間還需要一些交互,例如:
讀過(guò)程中數(shù)據(jù)源不可用,需要停止使用
使用過(guò)程中業(yè)務(wù)結(jié)束,需要中止數(shù)據(jù)源。
Reader和Writer都有一個(gè)Complete函數(shù),用于通知結(jié)束:
reader.Complete(); writer.Complete();
在Writer寫(xiě)入和Reader讀取時(shí),會(huì)獲得一個(gè)結(jié)果
FlushResult?result?=?await?writer.FlushAsync(); ReadResult?result?=?await?reader.ReadAsync();
它們都有一個(gè)IsComplete屬性,可以根據(jù)它是否為true判斷是否已經(jīng)結(jié)束了讀和寫(xiě)的操作。
取消
在寫(xiě)入和讀取的時(shí)候,也可以傳入一個(gè)CancellationToken,用于取消相應(yīng)的操作。
writer.FlushAsync(CancellationToken.None); reader.ReadAsync(CancellationToken.None);
如果取消成功,對(duì)應(yīng)的Result的IsCanceled則為true(沒(méi)有驗(yàn)證過(guò))
到此這篇關(guān)于C#高效IO庫(kù)System.IO.Pipelines的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#實(shí)現(xiàn)Zip壓縮目錄中所有文件的方法
這篇文章主要介紹了C#實(shí)現(xiàn)Zip壓縮目錄中所有文件的方法,涉及C#針對(duì)文件的讀寫(xiě)與zip壓縮相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07C#多線(xiàn)程處理多個(gè)隊(duì)列數(shù)據(jù)的方法
這篇文章主要介紹了C#多線(xiàn)程處理多個(gè)隊(duì)列數(shù)據(jù)的方法,涉及C#線(xiàn)程與隊(duì)列的相關(guān)操作技巧,需要的朋友可以參考下2015-07-07解析C#網(wǎng)絡(luò)編程中的Http請(qǐng)求
這篇文章主要介紹了C#網(wǎng)絡(luò)編程中的Http請(qǐng)求,不過(guò)這次也使我對(duì)C#網(wǎng)絡(luò)編程了解的更多,算是一次學(xué)習(xí)經(jīng)歷吧,文章結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03