简体   繁体   English

TPL数据流:如何限制整个管道?

[英]TPL Dataflow : How to throttle an entire pipeline?

I want to limit the number of items posted in a Dataflow pipeline. 我想限制在Dataflow管道中发布的项目数。 The number of items depends of the production environment. 项目数取决于生产环境。 These objects consume a large amount of memory (images) so I would like to post them when the last block of the pipeline has done its job. 这些对象占用大量内存(图像),因此我想在管道的最后一个块完成其工作时发布它们。

I tried to use a SemaphoreSlim to throttle the producer and release it in the last block of the pipeline. 我尝试使用SemaphoreSlim来限制生产者并在管道的最后一个块中释放它。 It works, but if an exception is raised during the process, the program waits forever and the exception is not intercepted. 它可以工作,但如果在此过程中引发异常,程序将永远等待,并且不会拦截异常。

Here is a sample which looks like our code. 这是一个看起来像我们的代码的示例。 How can I do this ? 我怎样才能做到这一点 ?

static void Main(string[] args)
{
    SemaphoreSlim semaphore = new SemaphoreSlim(1, 2);

    var downloadString = new TransformBlock<string, string>(uri =>
    {
        Console.WriteLine("Downloading '{0}'...", uri);
        return new WebClient().DownloadString(uri);
    });

    var createWordList = new TransformBlock<string, string[]>(text =>
    {
        Console.WriteLine("Creating word list...");

        char[] tokens = text.ToArray();
        for (int i = 0; i < tokens.Length; i++)
        {
            if (!char.IsLetter(tokens[i]))
                tokens[i] = ' ';
        }
        text = new string(tokens);

        return text.Split(new char[] { ' ' },
           StringSplitOptions.RemoveEmptyEntries);
    });

    var filterWordList = new TransformBlock<string[], string[]>(words =>
    {
        Console.WriteLine("Filtering word list...");
        throw new InvalidOperationException("ouch !"); // explicit for test
        return words.Where(word => word.Length > 3).OrderBy(word => word)
           .Distinct().ToArray();
    });

    var findPalindromes = new TransformBlock<string[], string[]>(words =>
    {
        Console.WriteLine("Finding palindromes...");

        var palindromes = new ConcurrentQueue<string>();

        Parallel.ForEach(words, word =>
        {
            string reverse = new string(word.Reverse().ToArray());

            if (Array.BinarySearch<string>(words, reverse) >= 0 &&
                word != reverse)
            {
                palindromes.Enqueue(word);
            }
        });

        return palindromes.ToArray();
    });

    var printPalindrome = new ActionBlock<string[]>(palindromes =>
    {
        try
        {
            foreach (string palindrome in palindromes)
            {
                Console.WriteLine("Found palindrome {0}/{1}",
                   palindrome, new string(palindrome.Reverse().ToArray()));
            }
        }
        finally
        {
            semaphore.Release();
        }
    });

    downloadString.LinkTo(createWordList);
    createWordList.LinkTo(filterWordList);
    filterWordList.LinkTo(findPalindromes);
    findPalindromes.LinkTo(printPalindrome);


    downloadString.Completion.ContinueWith(t =>
    {
        if (t.IsFaulted) 
            ((IDataflowBlock)createWordList).Fault(t.Exception);
        else createWordList.Complete();
    });
    createWordList.Completion.ContinueWith(t =>
    {
        if (t.IsFaulted) 
            ((IDataflowBlock)filterWordList).Fault(t.Exception);
        else filterWordList.Complete();
    });
    filterWordList.Completion.ContinueWith(t =>
    {
        if (t.IsFaulted) 
            ((IDataflowBlock)findPalindromes).Fault(t.Exception); // enter here when an exception throws
        else findPalindromes.Complete();
    });
    findPalindromes.Completion.ContinueWith(t =>
    {
        if (t.IsFaulted)
            ((IDataflowBlock)printPalindrome).Fault(t.Exception); // the fault is propagated here but not catched
        else printPalindrome.Complete();
    });

    try
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(i);

            downloadString.Post("http://www.google.com");
            semaphore.Wait(); // waits here when an exception throws
        }

        downloadString.Complete();

        printPalindrome.Completion.Wait();
    }
    catch (AggregateException agg)
    {
        Console.WriteLine("An error has occured : " + agg);
    }
    Console.WriteLine("Done");
    Console.ReadKey();
}

You should simply wait on both the semaphore and the completion task together. 您应该一起等待信号量和完成任务。 In that way if the block ends prematurely (either by exception or cancellation) then the exception will be rethrown and if not then you will wait on your semaphore until there's room to post more. 以这种方式,如果块过早地结束(通过异常或取消),则异常将被重新抛出,如果没有,那么您将等待您的信号量,直到有空间发布更多。

You can do that with Task.WhenAny and SemaphoreSlim.WaitAsync : 你可以使用Task.WhenAnySemaphoreSlim.WaitAsync来做到这Task.WhenAny

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(i);
    downloadString.Post("http://www.google.com");

    if (printPalindrome.Completion.IsCompleted)
    {
        break;
    }

    Task.WhenAny(semaphore.WaitAsync(), printPalindrome.Completion).Wait();
}

Note: using Task.Wait is only appropriate in this case as it's Main . 注意:使用Task.Wait仅适用于此情况,因为它是Main Usually this should be an async method and you should await the task returned from Task.WhenAny . 通常这应该是async方法,您应该awaitTask.WhenAny返回的任务。

This is how I handled throttling or only allowing 10 items in the source block at any one time. 这就是我在任何时候处理限制或仅允许源块中的10个项目的方式。 You could modify this to have 1. Make sure that you also throttle any other blocks in the pipeline, otherwise, you could get the source block with 1 and the next block with a lot more. 您可以将其修改为1.确保您还限制管道中的任何其他块,否则,您可以获得带有1的源块和带有更多块的下一个块。

var sourceBlock = new BufferBlock<string>(
    new ExecutionDataflowBlockOptions() { 
        SingleProducerConstrained = true, 
        BoundedCapacity = 10 });

Then the producer does this: 然后制作人这样做:

sourceBlock.SendAsync("value", shutdownToken).Wait(shutdownToken);

If you're using async / await, just await the SendAsync call. 如果您正在使用async / await,则只需等待SendAsync调用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM