[英]TPL DataFlow Workflow
我剛剛開始閱讀TPL Dataflow,這對我來說真的很困惑。 我讀過很多關於這個主題的文章,但我無法輕易消化它。 可能很難,也許我還沒有開始掌握這個想法。
我之所以開始研究這個問題,是因為我想實現一個可以運行並行任務但按順序運行的場景,並發現TPL Dataflow可以用作這個。
我正在練習TPL和TPL Dataflow,而且我的初學者水平很高,所以我需要專家的幫助,他們可以指導我找到正確的方向。 在我寫的測試方法中,我做了以下事情,
private void btnTPLDataFlow_Click(object sender, EventArgs e)
{
Stopwatch watch = new Stopwatch();
watch.Start();
txtOutput.Clear();
ExecutionDataflowBlockOptions execOptions = new ExecutionDataflowBlockOptions();
execOptions.MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded;
ActionBlock<string> actionBlock = new ActionBlock<string>(async v =>
{
await Task.Delay(200);
await Task.Factory.StartNew(
() => txtOutput.Text += v + Environment.NewLine,
CancellationToken.None,
TaskCreationOptions.None,
scheduler
);
}, execOptions);
for (int i = 1; i < 101; i++)
{
actionBlock.Post(i.ToString());
}
actionBlock.Complete();
watch.Stop();
lblTPLDataFlow.Text = Convert.ToString(watch.ElapsedMilliseconds / 1000);
}
現在程序是並行的並且都是異步的(不凍結我的UI),但生成的輸出不是有序的,而我已經讀過TPL Dataflow默認保持元素的順序。 所以我的猜測是,然后我創建的任務是罪魁禍首,它不會以正確的順序輸出字符串。 我對嗎?
如果是這種情況,那么如何使這個異步並按順序進行?
我試圖分離代碼,並試圖將代碼分發到不同的方法,但我的嘗試失敗,因為只有字符串輸出到文本框,沒有其他事情發生。
private async void btnTPLDataFlow_Click(object sender, EventArgs e)
{
Stopwatch watch = new Stopwatch();
watch.Start();
await TPLDataFlowOperation();
watch.Stop();
lblTPLDataFlow.Text = Convert.ToString(watch.ElapsedMilliseconds / 1000);
}
public async Task TPLDataFlowOperation()
{
var actionBlock = new ActionBlock<int>(async values => txtOutput.Text += await ProcessValues(values) + Environment.NewLine,
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded, TaskScheduler = scheduler });
for (int i = 1; i < 101; i++)
{
actionBlock.Post(i);
}
actionBlock.Complete();
await actionBlock.Completion;
}
private async Task<string> ProcessValues(int i)
{
await Task.Delay(200);
return "Test " + i;
}
我知道我寫了一段糟糕的代碼,但這是我第一次嘗試使用TPL Dataflow。
如何使這個異步和按順序?
這有點矛盾。 您可以按順序啟動並發任務,但不能保證它們按順序運行或完成。
讓我們檢查一下你的代碼,看看發生了什么。
首先,您已選擇DataflowBlockOptions.Unbounded
。 這告訴TPL Dataflow它不應該限制它允許並發運行的任務數量。 因此,您的每一個任務將在更多或更少的同時啟動 ,為了。
您的異步操作以await Task.Delay(200)
。 這將導致您的方法暫停,然后在大約 200毫秒后恢復。 但是,這種延遲並不准確,並且從一次調用到下一次調用都會有所不同。 此外,延遲后恢復代碼的機制可能需要花費不同的時間。 由於實際延遲的這種隨機變化,然后運行的下一位代碼現在不按順序排列 - 導致您看到的差異。
您可能會發現此示例很有趣。 這是一個簡化操作的控制台應用程序。
class Program
{
static void Main(string[] args)
{
OutputNumbersWithDataflow();
OutputNumbersWithParallelLinq();
Console.ReadLine();
}
private static async Task HandleStringAsync(string s)
{
await Task.Delay(200);
Console.WriteLine("Handled {0}.", s);
}
private static void OutputNumbersWithDataflow()
{
var block = new ActionBlock<string>(
HandleStringAsync,
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded });
for (int i = 0; i < 20; i++)
{
block.Post(i.ToString());
}
block.Complete();
block.Completion.Wait();
}
private static string HandleString(string s)
{
// Perform some computation on s...
Thread.Sleep(200);
return s;
}
private static void OutputNumbersWithParallelLinq()
{
var myNumbers = Enumerable.Range(0, 20).AsParallel()
.AsOrdered()
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
.WithMergeOptions(ParallelMergeOptions.NotBuffered);
var processed = from i in myNumbers
select HandleString(i.ToString());
foreach (var s in processed)
{
Console.WriteLine(s);
}
}
}
第一組數字是使用與您的方法非常相似的方法計算的 - 使用TPL Dataflow。 這些數字是無序的。
OutputNumbersWithParallelLinq()
輸出的第二組數字根本不使用Dataflow。 它依賴於.NET內置的Parallel LINQ功能。 這在后台線程上運行我的HandleString()
方法,但是將數據按順序保存到最后 。
這里的限制是PLINQ不允許您提供異步方法。 (好吧,你可以,但它不會給你想要的行為。) HandleString()
是一種傳統的同步方法; 它只是在后台線程上執行。
這是一個更復雜的Dataflow示例,它保留了正確的順序 :
private static void OutputNumbersWithDataflowTransformBlock()
{
Random r = new Random();
var transformBlock = new TransformBlock<string, string>(
async s =>
{
// Make the delay extra random, just to be sure.
await Task.Delay(160 + r.Next(80));
return s;
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded });
// For a GUI application you should also set the
// scheduler here to make sure the output happens
// on the correct thread.
var outputBlock = new ActionBlock<string>(
s => Console.WriteLine("Handled {0}.", s),
new ExecutionDataflowBlockOptions
{
SingleProducerConstrained = true,
MaxDegreeOfParallelism = 1
});
transformBlock.LinkTo(outputBlock, new DataflowLinkOptions { PropagateCompletion = true });
for (int i = 0; i < 20; i++)
{
transformBlock.Post(i.ToString());
}
transformBlock.Complete();
outputBlock.Completion.Wait();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.