保证TransformBlock输出序列

时间:2015-06-16 16:24:29

标签: c# multithreading async-await task-parallel-library tpl-dataflow

来自TPL文档

  

ActionBlock<TInput>一样,TransformBlock<TInput,TOutput>默认值   一次处理一条消息,保持严格的FIFO排序。

但是,在多线程场景中,即如果多个线程“同时”执行SendAsync然后通过调用ReceiveAsync“等待”结果,我们如何保证该线程在TransformBlock<TInput,TOutput>中发布的东西实际上得到了它正在等待的预期结果?

在我的实验中,似乎“保证”我想要的结果的方法是添加选项BoundedCapacity = 1。发送和接收时,至少线程仍然没有被阻止。

如果我不这样做,一些线程将收到另一个线程的结果。

在这个特定的用例中这是正确的方法吗?

以下是一些说明我关注的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace ConsoleTransformBlock
{
    class Program
    {
        private readonly static TransformBlock<int, int> _pipeline;

        static Program()
        {

            _pipeline = new TransformBlock<int, int>(async (input) =>
            {
                await Task.Delay(RandomGen2.Next(5, 100)).ConfigureAwait(false);
                return input;

            }, 
            new ExecutionDataflowBlockOptions() { BoundedCapacity = 1 }); // this is the fix???
        }


        static void Main(string[] args)
        {
            var dop = System.Environment.ProcessorCount;// 8-core


            Parallel.For(0, dop, new ParallelOptions() { MaxDegreeOfParallelism = dop },
                 (d) =>
                 {
                     DoStuff().Wait();
                 });

            Console.WriteLine("Parallel For Done ...");
            var tasks = new Task[dop];
            for (var i = 0; i < dop; i++)
            {
             var temp = i;
             tasks[temp] = Task.Factory.StartNew
                (async () => await DoStuff().ConfigureAwait(false),
                CancellationToken.None,
                TaskCreationOptions.LongRunning,
                TaskScheduler.Default).Unwrap();
            }

            Task.WaitAll(tasks);


        }

        private static async Task DoStuff()
        {
            for (var i = 0; i < 100; i++)
            {
                var temp = RandomGen2.Next();
                await _pipeline.SendAsync(temp).ConfigureAwait(false);
                Console.WriteLine("Just sent {0}, now waiting {1}...", new object[] { temp, System.Threading.Thread.CurrentThread.ManagedThreadId });
                await Task.Delay(RandomGen2.Next(5, 50)).ConfigureAwait(false);
                var result = await _pipeline.ReceiveAsync().ConfigureAwait(false);
                Console.WriteLine("Received {0}... {1}", new object[] { result, System.Threading.Thread.CurrentThread.ManagedThreadId });

                if (result != temp)
                {
                    var error = string.Format("************** Sent {0} But Received {1}", temp, result, System.Threading.Thread.CurrentThread.ManagedThreadId);
                    Console.WriteLine(error);
                    break;

                }

            }
        }

        /// <summary>
        /// Thread-Safe Random Generator
        /// </summary>
        public static class RandomGen2
        {
            private static Random _global = new Random();
            [ThreadStatic]
            private static Random _local;

            public static int Next()
            {
                return Next(0, int.MaxValue);
            }
            public static int Next(int max)
            {
                return Next(0, max);
            }
            public static int Next(int min, int max)
            {
                Random inst = _local;
                if (inst == null)
                {
                    int seed;
                    lock (_global) seed = _global.Next();
                    _local = inst = new Random(seed);
                }
                return inst.Next(min, max);
            }
        }
    }
}

1 个答案:

答案 0 :(得分:4)

TransformBlock已经保持了FIFO顺序。将项目发布到块的顺序是从块返回项目的确切顺序。

  

如果指定的最大并行度大于1,则会同时处理多个消息,因此,可能无法按接收顺序处理消息。但是,从块输出消息的顺序将正确排序。

来自Dataflow (Task Parallel Library)

您可以通过此示例看到:

private static async Task MainAsync()
{
    var transformBlock = new TransformBlock<int, int>(async input =>
    {
        await Task.Delay(RandomGen2.Next(5, 100));
        return input;
    }, new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 10});

    foreach (var number in Enumerable.Range(0,100))
    {
        await transformBlock.SendAsync(number);
    }

    for (int i = 0; i < 100; i++)
    {
        var result = await transformBlock.ReceiveAsync();
        Console.WriteLine(result);
    }
}

订单将在0-99之间订购。

但是,您似乎想要的是与线程的某些关联,因此线程会将项目发布到块然后接收其结果。这并不适合TPL数据流,这应该是更多的块流水线。你可以用BoundedCapacity = 1破解它,但你可能不应该。