Tuesday, May 17, 2011

TPL Dataflow WriteOnceBlock Features

TPL Dataflow library brings support for parallel dataflow-based programming into the .NET Framework .NET 4.0.

One of the building blocks of Dataflow is WriteOnceBlock<T>. Documentation expresses its usage in the simple manner - It stores at most one value, and once that value has been set, it will never be replaced or overwritten. All consumers may obtain a copy of the stored value. You can think of WriteOnceBlock<T> in TPL Dataflow as being similar to a readonly member variable in C#, except instead of only being settable in a constructor and then being immutable, it’s only settable once and is then immutable.


Clear enough. Let's try to demonstrate this:
ActionBlock<Int32> writeToConsole1 = new ActionBlock<Int32>( integer => Console.WriteLine( "Console 1: " + integer ) );

// true if the source should unlink from the target after successfully propagating a single message
// otherwise, false to remain connected even after a single message has been propagated
bool unlinkAfterOne = false;

WriteOnceBlock<Int32> writeOnceBlock1 = new WriteOnceBlock<Int32>( integer => integer );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );

writeOnceBlock1.Post( 12 );
writeOnceBlock1.Post( 22 );
writeOnceBlock1.Post( 23 );

// prints via Console 1 only one value - the first one:
// Console 1: 12

We composed the data flow from two blocks - WriteOnceBlock comes the first and links to ActionBlock which prints to console all values it receives. As it was stated correctly the snippet above prints only the first value passed to the chain - 12. So it works as expected.

But let's go ahead and investigate the other advantages of WriteOnceBlock.
Let's link the same ActionBlock to WriteOnceBlock many times.

WriteOnceBlock<Int32> writeOnceBlock1 = new WriteOnceBlock<Int32>( integer => integer );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );

writeOnceBlock1.Post( 12 );
// prints via Console 1 five times:
// Console 1: 12
// Console 1: 12
// Console 1: 12
// Console 1: 12
// Console 1: 12

The same value is printed five times what gives us the truth - linked targets are not unique within the block. You can register the same target block many times and all of them would executed. So be careful and don't execute LinkTo unnecessarily.

The understanding of the following feature actually gives you the real world usage of WriteOnceBlock.

ActionBlock<Int32> writeToConsole1 = new ActionBlock<Int32>( integer => Console.WriteLine( "Console 1: " + integer ) );

// true if the source should unlink from the target after successfully propagating a single message
// otherwise, false to remain connected even after a single message has been propagated
bool unlinkAfterOne = false;

WriteOnceBlock<Int32> writeOnceBlock1 = new WriteOnceBlock<Int32>( integer => integer );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );

// prints 12 via Console 1:
// Console 1: 12
writeOnceBlock1.Post( 12 );

// create 4 additional Targets
ActionBlock<Int32> writeToConsole2 = new ActionBlock<Int32>( integer => Console.WriteLine( "Console 2: " + integer ) );
ActionBlock<Int32> writeToConsole3 = new ActionBlock<Int32>( integer => Console.WriteLine( "Console 3: " + integer ) );
ActionBlock<Int32> writeToConsole4 = new ActionBlock<Int32>( integer => Console.WriteLine( "Console 4: " + integer ) );
ActionBlock<Int32> writeToConsole5 = new ActionBlock<Int32>( integer => Console.WriteLine( "Console 5: " + integer ) );

// link those Targets to WriteOnceBlock which already holds a value
writeOnceBlock1.LinkTo( writeToConsole2, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole3, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole4, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole5, unlinkAfterOne );

// prints the following:
// Console 2: 12
// Console 3: 12
// Console 4: 12
// Console 5: 12

Did you get it? When WriteOnceBlock already holds a value any newly linked target block will receive that value. Even if we link the same block over and over again - we already know that we can. Linking the same target block four times will lead to its execution four times.

// link the same ActionBlock four times will lead to its execution four times
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );
writeOnceBlock1.LinkTo( writeToConsole1, unlinkAfterOne );

// prints the following:
// Console 1: 12
// Console 1: 12
// Console 1: 12
// Console 1: 12

Further reading:
TPL Dataflow Library on DevLabs website
Parallel Computing Developer Center
Parallel Programming with .NET - TPL Dataflow Library Team Blog

No comments:

Post a Comment