(Collects 2-3 inputs and combines them into a Tuple)
Like BatchBlock, JoinBlock<T1, T2, …> is able to group data from multiple data sources. In fact, that’s JoinBlock<T1, T2, …>’s primary purpose.
For example, a JoinBlock<string, double, int> is an ISourceBlock<Tuple<string, double, int>>.
As with BatchBlock, JoinBlock<T1, T2,…> is capable of operating in both greedy and non-greedy mode.
Processing Requests with a Limited Number of Pooled Objects
var throttle = new JoinBlock<ExpensiveObject, Request>();
for(int i=0; i<10; i++) 
{
    requestProcessor.Target1.Post(new ExpensiveObject()); 
}
var processor = new Transform<Tuple<ExpensiveObject, Request>, ExpensiveObject>(pair =>
{
    var resource = pair.Item1;
    var request = pair.Item2;
    
    request.ProcessWith(resource);
    
    return resource;
});
throttle.LinkTo(processor);
processor.LinkTo(throttle.Target1);
Introduction to TPL Dataflow by Stephen Toub