簡體   English   中英

TPL DataFlow工作流程

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM