public static IPropagatorBlock <SplitJoinItem <TParent, TItem>, SplitJoinResult <TParent, TItem> > CreateJoinBlock <TParent, TItem> ()
        {
            var intermediate_results = new Dictionary <TParent, SplitJoinIntermediateResult <TItem> > ();

            var block = new TransformManyBlock <SplitJoinItem <TParent, TItem>, SplitJoinResult <TParent, TItem> >
                            (x =>
            {
                if (x.Result == null)
                {
                    x.CompletedSuccessfully();
                }

                SplitJoinIntermediateResult <TItem> intermediate_result;
                if (!intermediate_results.TryGetValue(x.Parent, out intermediate_result))
                {
                    intermediate_result            = new SplitJoinIntermediateResult <TItem> (x.TotalItemsCount);
                    intermediate_results[x.Parent] = intermediate_result;
                }

                if (intermediate_result.Completed(x))
                {
                    var split_join_result = new SplitJoinResult <TParent, TItem> (x.Parent, intermediate_result);
                    return(new[] { split_join_result });
                }

                return(new SplitJoinResult <TParent, TItem> [0]);
            },
                            new ExecutionDataflowBlockOptions
            {
                BoundedCapacity        = DataflowBlockOptions.Unbounded,
                MaxDegreeOfParallelism = 1
            });

            return(block);
        }
        public static IPropagatorBlock <SplitJoinItem <TParent, TItem>, TOutput> CreateJoinBlockAsync <TParent, TItem, TOutput>
            ([NotNull] Func <SplitJoinResult <TParent, TItem>, Task <TOutput> > process,
            Action <Exception, SplitJoinResult <TParent, TItem> > defaultExceptionLogger = null)
        {
            if (process == null)
            {
                throw new ArgumentNullException(nameof(process));
            }

            var intermediate_results = new Dictionary <TParent, SplitJoinIntermediateResult <TItem> > ();

            var block = new TransformManyBlock <SplitJoinItem <TParent, TItem>, TOutput>
                            (async x =>
            {
                if (x.Result == null)
                {
                    x.CompletedSuccessfully();
                }

                SplitJoinIntermediateResult <TItem> intermediate_result;
                if (!intermediate_results.TryGetValue(x.Parent, out intermediate_result))
                {
                    intermediate_result            = new SplitJoinIntermediateResult <TItem> (x.TotalItemsCount);
                    intermediate_results[x.Parent] = intermediate_result;
                }

                if (intermediate_result.Completed(x))
                {
                    var split_join_result = new SplitJoinResult <TParent, TItem> (x.Parent, intermediate_result);

                    try
                    {
                        var result = await process(split_join_result).ConfigureAwait(false);

                        return(new[] { result });
                    }
                    catch (Exception ex)
                    {
                        var logger = x.Parent as IDataflowErrorLogger;
                        if (logger != null)
                        {
                            logger.OnException(ex);
                        }
                        else if (defaultExceptionLogger != null)
                        {
                            defaultExceptionLogger(ex, split_join_result);
                        }
                    }
                }

                return(new TOutput[0]);
            },
                            new ExecutionDataflowBlockOptions
            {
                BoundedCapacity        = DataflowBlockOptions.Unbounded,
                MaxDegreeOfParallelism = 1
            });

            return(block);
        }
        public static ITargetBlock <SplitJoinItem <TParent, TItem> > CreateFinalJoinBlock <TParent, TItem> (
            [NotNull] Action <SplitJoinResult <TParent, TItem> > process,
            Action <Exception, SplitJoinResult <TParent, TItem> > defaultExceptionLogger = null)
        {
            if (process == null)
            {
                throw new ArgumentNullException(nameof(process));
            }

            var intermediate_results = new Dictionary <TParent, SplitJoinIntermediateResult <TItem> > ();

            var block = new ActionBlock <SplitJoinItem <TParent, TItem> >
                            (x =>
            {
                if (x.Result == null)
                {
                    x.CompletedSuccessfully();
                }

                SplitJoinIntermediateResult <TItem> intermediate_result;
                if (!intermediate_results.TryGetValue(x.Parent, out intermediate_result))
                {
                    intermediate_result            = new SplitJoinIntermediateResult <TItem> (x.TotalItemsCount);
                    intermediate_results[x.Parent] = intermediate_result;
                }

                if (intermediate_result.Completed(x))
                {
                    var split_join_result = new SplitJoinResult <TParent, TItem> (x.Parent, intermediate_result);
                    try
                    {
                        process(split_join_result);
                    }
                    catch (Exception ex)
                    {
                        var logger = x.Parent as IDataflowErrorLogger;
                        if (logger != null)
                        {
                            logger.OnException(ex);
                        }
                        else if (defaultExceptionLogger != null)
                        {
                            defaultExceptionLogger(ex, split_join_result);
                        }
                    }
                }
            },
                            new ExecutionDataflowBlockOptions
            {
                BoundedCapacity        = DataflowBlockOptions.Unbounded,
                MaxDegreeOfParallelism = 1
            });

            return(block);
        }