[英]Pipeline pattern and disposable objects
最近,我開始研究Pipeline
模式,也稱為Pipes and Filters
。 我認為這是構造代碼和處理數據的應用程序的好方法。 我將本文用作管道和步驟實現的基礎(但這並不是那么重要)。 像往常一樣,博客只涉及簡單的場景,但就我而言,我需要(或可能不需要)處理可能在過程中傳播的IDisposable
對象。
例如流
讓我們考慮一個簡單的管道,該管道應加載csv文件並將其行插入某些db中。 簡單地講,我們可以實現這樣的功能
Stream Step1(string filePath)
IEnumerable<RowType> Step2(Stream stream)
bool Step3(IEnumerable<RowType> data)
現在我的問題是,這是否是一個好方法。 因為如果我們在逐步處理中實現該目標,則Stream
對象將離開第一步,並且很容易陷入內存泄漏問題。 我知道有人可能會說我應該擁有要加載和反序列化數據的Step1
,但是我們正在考慮簡單的過程。 我們可能會有更復雜的方法,在這些方法中通過Stream更為合理。
我想知道如何實現這樣的管道以避免內存泄漏,也避免將整個文件加載到MemoryStream
(這樣會更安全)。 如果出現問題,是否應該以某種方式將每個步驟包裝在try..catch
塊中以調用Dispose()
? 或者我應該通過所有IDisposable
資源投入到Pipeline
對象,將被包裝using
處置正確處理過程中產生的所有的資源?
如果計划像Step3( Step2( Step1(filePath) ) )
,則Step2
應該處理流。 它可以使用c#的yield return
功能,該功能在下面創建IEnumerator <>的實現,該實現實現IDisposable
,並允許“訂閱”完成枚舉的“事件”並調用Stream.Dispose
。 例如:
IEnumerable<RowType> Step2(Stream stream)
{
using(stream)
using(StreamReader sr = new StreamReader(stream))
{
while(!sr.EndOfStream)
{
yield return Parse(sr.ReadLine()); //yield return implements IEnumerator<>
}
} // finally part of the using will be called from IEnumerator<>.Dispose()
}
然后,如果Step3
使用LINQ
bool Step3(IEnumerable<RowType> data) => data.Any(item => SomeDecisionLogic(item));
或foreach
bool Step3(IEnumerable<RowType> data)
{
foreach(var item in data)
if(SomeDecisionLogic(item)))
return true;
}
為了枚舉,它們兩個都保證調用IEnumerator<>.Dispose()
( ref1 , ECMA-334 C#Spec,ch.13.9.5 ),這將調用Stream.Dispose
如果至少在兩個不同的系統之間進行交互並且可以並行執行工作,則IMO值得擁有一條管道。 否則會增加開銷。
在這種情況下,有2個系統:CSV文件所在的文件系統和數據庫。 我認為管道應該至少有兩個並行運行的步驟:
IEnumerable<Row> ReadFromCsv(string csvFilePath)
void UpdateDabase<IEnumerable<Row> rows)
在這種情況下,應該很清楚Stream綁定到ReadFromCsv
。
IEnumerable<Row> ReadFromCsv(path)
{
using(var stream = File.OpenRead(path))
{
var lines = GetLines(stream); // yield one at a time, not all at once
foreach (var line in line) yield return GetRow(line);
}
}
我認為范圍取決於步驟-而步驟又取決於您根據需要設計管道的方式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.