.NET併發程式設計-TPL Dataflow並行工作流

那是山發表於2021-05-09

本系列學習在.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(0100);
    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

第一次做人,何不痛痛快快,瀟瀟灑灑,討好自己

工作認認真真的完成,生活充充實實的過著

相關文章