.NET併發編程-TPL Dataflow並行工作流

本系列學習在.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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章