本系列學習在.NET中的併發並行程式設計模式,實戰技巧
本小節瞭解TPL Dataflow並行工作流,在工作中如何利用現成的類庫處理資料。旨在通過TDF實現資料流的並行處理。
TDF Block
資料流由一個一個的塊組成,一個塊處理完畢後連結到下一個塊上。每一個塊以訊息的形式接收和快取來自一個或多個源的資料,當接收到資訊時,塊通過將其行為應用於輸入來作出反應,塊的輸出將傳遞到下一個塊中。
TDF並不是作為.NET4.5框架的一部分分發,需要單獨安裝,用過nuget匯入Microsoft.Tpl.Dataflow。4.5之上在System.Threading.Tasks.Dataflow類庫中。TDF提供了一組豐富的元件(塊),用於基於程式內訊息傳遞語義來組合資料流和管道基礎設施。
TDF最常用的塊是標準的BufferBlock、ActionBlock和TransformBlock。它們每個都基於一個委託,該委託可以是匿名函式的形式,用於定義要計算的工作。
BufferBlock<TInput>
BufferBlock是一個很好的工具,用於啟用和實現非同步生產者/消費者模式,其中內部的訊息佇列可以由多個源寫入或從多個目標讀取。保證先進先出的順序。
以下展示基於TDF BufferBlock的生產者消費者模式
BufferBlock<int> buffer = new BufferBlock<int>();
async Task Producer(IEnumerable<int> values)
{
foreach (var value in values)
await buffer.SendAsync(value);
buffer.Complete();
}
async Task Consumer(Action<int> process)
{
while (await buffer.OutputAvailableAsync())
process(await buffer.ReceiveAsync());
}
public async Task Run()
{
IEnumerable<int> range = Enumerable.Range(0, 100);
await Task.WhenAll(Producer(range), Consumer(n =>
Console.WriteLine($"value {n}")));
}
IEnumerable值的條目通過buffer.Post方法傳送到BufferBlock緩衝區,並使用buffer.ReceiveAsync方法非同步檢索它們。OutputAvailableAsync方法用於當下一個條目準備好可被檢索時發出通知。
TransformBlock<TInput,TOutput>
用於對映轉換,該轉換函式以委託Func<TInput,TOutput>的形式作為引數傳遞
給定一組地址下載圖片為例
var fetchImageFlag = new TransformBlock<string, (string, byte[])>(
async urlImage =>
{
using (var webClient = new WebClient())
{
byte[] data = await webClient.DownloadDataTaskAsync(urlImage);
return (urlImage, data);
}
});
List<string> urlFlags = new List<string>{
"Italy#/media/File:Flag_of_Italy.svg",
"Spain#/media/File:Flag_of_Spain.svg",
"United_States#/media/File:Flag_of_the_United_States.svg"
};
foreach (var urlFlag in urlFlags)
fetchImageFlag.Post($"https://en.wikipedia.org/wiki/{urlFlag}");
TransformBlock<string, (string, byte[]) 塊以元組字串和位元組陣列格式來提取標記影像。轉換得到位元組陣列物件後,此處還沒有消費使用。下面通過另一個塊組合將其儲存到本地。
ActionBlock<TInput>
通過名稱就可以看出,該塊用於接收資料時呼叫一個委託去處理。因為它沒有輸出,所以通常用於工作流的結束節點上。
前面通過轉換塊將圖片地址下載轉換成了位元組陣列,下面通過ActionBlock將其持久化本地。
var saveData = new ActionBlock<(string, byte[])>(async data =>
{
(string urlImage, byte[] image) = data;
string filePath = urlImage.Substring(urlImage.IndexOf("File:") + 5);
await Agents.File.WriteAllBytesAsync(filePath, image);
});
fetchImageFlag.LinkTo(saveData);
ActionBlock塊例項化傳遞給建構函式的引數可以是委託Action或Func<TInput,Task>。後者對每個訊息輸入非同步執行內部操作。最後ActionBlock塊saveData使用LinkTo擴充套件方法連線到前面的TransformBlock塊上。通過這種方式,TransformBlock生成的輸出會在可用時被立即推送到ActionBlock中。
最後貼上一下File的擴充套件方法,用於非同步讀寫檔案。
public static class File
{
public static async Task<string[]> ReadAllLinesAsync(string path)
{
using (var sourceStream = new FileStream(path,
FileMode.Open, FileAccess.Read, FileShare.None,
bufferSize: 4096, useAsync: true))
using (var reader = new StreamReader(sourceStream))
{
var fileText = await reader.ReadToEndAsync();
return fileText.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
}
}
public static async Task WriteAllTextAsync(string path, string contents)
{
byte[] encodedText = Encoding.Unicode.GetBytes(contents);
await WriteAllBytesAsync(path, encodedText);
}
public static async Task WriteAllBytesAsync(string path, byte[] bytes)
{
using (var sourceStream = new FileStream(path,
FileMode.Append, FileAccess.Write, FileShare.None,
bufferSize: 4096, useAsync: true))
{
await sourceStream.WriteAsync(bytes, 0, bytes.Length);
};
}
}
ending
第一次做人,何不痛痛快快,瀟瀟灑灑,討好自己
工作認認真真的完成,生活充充實實的過著