C#中的高效IO庫System.IO.Pipelines
我們在編寫網(wǎng)絡(luò)程序的時候,經(jīng)常會進行如下操作:
申請一個緩沖區(qū)
從數(shù)據(jù)源中讀入數(shù)據(jù)至緩沖區(qū)
解析緩沖區(qū)的數(shù)據(jù)
重復(fù)第2步
表面上看來這是一個很常規(guī)而簡單的操作,但實際使用過程中往往存在如下痛點:
數(shù)據(jù)讀不全:
可能不能在一次read操作中讀入所有需要的數(shù)據(jù),因此需要在緩沖區(qū)中維護一個游標(biāo),記錄下次讀取操作的起始位置,這個游標(biāo)帶了了不小的復(fù)雜度:
從緩沖區(qū)讀數(shù)據(jù)時,要根據(jù)游標(biāo)計算緩沖區(qū)起始寫位置,以及剩余空間大小。增加了讀數(shù)據(jù)的復(fù)雜度。
解析數(shù)據(jù)也是復(fù)用這個緩沖區(qū)的,解析的時候也要判斷游標(biāo)起始位置,剩余空間大小。同時增加了解析數(shù)據(jù)的復(fù)雜度。
解析玩了后還要移動游標(biāo),重新標(biāo)記緩沖區(qū)起始位置,再次增加了復(fù)雜度。
緩沖區(qū)容量有限:
由于緩沖區(qū)有限,可能申請的緩沖區(qū)不夠用,需要引入動態(tài)緩沖區(qū)。這也大幅加大了代碼的復(fù)雜度。
如果每次都申請更大的內(nèi)存,一方面帶來的內(nèi)存申請釋放開銷,另一方面需要將原來的數(shù)據(jù)移動,并更新游標(biāo),帶來更復(fù)雜的邏輯。
如果靠多段的內(nèi)存組成一個邏輯整理,數(shù)據(jù)的讀寫方式都比較復(fù)雜。
使用完后的內(nèi)存要釋放,如果需要更高的效率還要維持一個內(nèi)存池。
讀和用沒有分離
我們的業(yè)務(wù)本身只關(guān)心使用操作,但讀和用操作沒有分離,復(fù)雜的都操作導(dǎo)致用操作也變得復(fù)雜,并且嚴重干擾業(yè)務(wù)邏輯。
今天介紹微軟新推出的一個庫:System.IO.Pipelines(需要在Nuget上安裝),用于解決這些痛點。它主要包含一個Pipe對象,它有一個Writer屬性和Reader屬性。
var pipe = new Pipe(); var writer = pipe.Writer; var reader = pipe.Reader;
Writer對象
Writer對象用于從數(shù)據(jù)源讀取數(shù)據(jù),將數(shù)據(jù)寫入管道中;它對應(yīng)業(yè)務(wù)中的"讀"操作。
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對象用于從管道中獲取數(shù)據(jù)源,它對應(yīng)業(yè)務(wù)中的"用"操作。
首先獲取管道的緩沖區(qū):
var?result?=?await?reader.ReadAsync(); var?buffer?=?result.Buffer;
這個Buffer是一個ReadOnlySequence<byte>對象,它是一個相當(dāng)好的動態(tài)內(nèi)存對象,并且相當(dāng)高效。它本身由多段Memory<byte>組成,查看Memory段的方法有:
IsSingleSegment: 判斷是否只有一段Memory<byte>
First: 獲取第一段Memory<byte>
GetEnumerator: 獲取分段的Memory<byte>
它從邏輯上也可以看成一段連續(xù)的Memory<byte>,也有類似的方法:
Length: 整個數(shù)據(jù)緩沖區(qū)長度
Slice: 分割緩沖區(qū)
CopyTo: 將內(nèi)容復(fù)制到Span中
ToArray: 將內(nèi)容復(fù)制到byte[]中
另外,它還有一個類似游標(biāo)的位置對象SequencePosition,可以從其Position相關(guān)函數(shù)中使用,這里就不多介紹了。
這個緩沖區(qū)解決了"數(shù)據(jù)讀不夠"的問題,一次讀取的不夠下次可以接著讀,不用緩沖區(qū)的動態(tài)分配,高效的內(nèi)存管理方式帶來了良好的性能,好用的接口是我們能更關(guān)注業(yè)務(wù)。
獲取到緩沖區(qū)后,就是使用緩沖區(qū)的數(shù)據(jù)
var?data?=?buffer.ToArray();
使用完后,告訴PIPE當(dāng)前使用了多少數(shù)據(jù),下次接著從結(jié)束位置后讀起
reader.AdvanceTo(buffer.GetPosition(4));
這是一個相當(dāng)實用的設(shè)計,它解決了"讀了就得用"的問題,不僅可以將不用的數(shù)據(jù)下次再使用,還可以實現(xiàn)Peek的操作,只讀但不改變游標(biāo)。
交互
除了"讀"和"用"操作外,它們之間還需要一些交互,例如:
讀過程中數(shù)據(jù)源不可用,需要停止使用
使用過程中業(yè)務(wù)結(jié)束,需要中止數(shù)據(jù)源。
Reader和Writer都有一個Complete函數(shù),用于通知結(jié)束:
reader.Complete(); writer.Complete();
在Writer寫入和Reader讀取時,會獲得一個結(jié)果
FlushResult?result?=?await?writer.FlushAsync(); ReadResult?result?=?await?reader.ReadAsync();
它們都有一個IsComplete屬性,可以根據(jù)它是否為true判斷是否已經(jīng)結(jié)束了讀和寫的操作。
取消
在寫入和讀取的時候,也可以傳入一個CancellationToken,用于取消相應(yīng)的操作。
writer.FlushAsync(CancellationToken.None); reader.ReadAsync(CancellationToken.None);
如果取消成功,對應(yīng)的Result的IsCanceled則為true(沒有驗證過)
到此這篇關(guān)于C#高效IO庫System.IO.Pipelines的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C# paddlerocrsharp識別身份證號的實現(xiàn)示例
paddlerocrsharp可以進行圖片識別,本文主要介紹了C# paddlerocrsharp識別身份證號的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下2024-02-02
詳談C# 圖片與byte[]之間以及byte[]與string之間的轉(zhuǎn)換
下面小編就為大家?guī)硪黄斦凜# 圖片與byte[]之間以及byte[]與string之間的轉(zhuǎn)換。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02
c# winform取消右上角關(guān)閉按鈕的實現(xiàn)方法
本文是對c#中winform取消右上角關(guān)閉按鈕的實現(xiàn)方法進行了詳細的介紹,需要的朋友可以過來參考下。希望對大家有所幫助2013-10-10
Quartz.Net任務(wù)和觸發(fā)器實現(xiàn)方法詳解
這篇文章主要介紹了Quartz.Net任務(wù)和觸發(fā)器實現(xiàn)方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-12-12

