Write Once Block
The represented code is divided into three main parts: Player class, IWriteOnceBlockService interface, and DemoWriteOnceBlock class.
Player - A simple class that serves as a model for Player objects. Each Player object has an Id (automatically generated), Name, and HasReceivedRedCard boolean property.
IWriteOnceBlockService<T> - This is an interface that enforces a Post method which accepts a list of items of generic type T where T is any class.
DemoWriteOnceBlock<T> - A class that implements IWriteOnceBlockService<T>. DemoWriteOnceBlock internally uses WriteOnceBlock<T> from the TPL Dataflow library. WriteOnceBlock<T> is a dataflow block that stores at most one element.
In the DemoWriteOnceBlock constructor, a new WriteOnceBlock is created. The WriteOnceBlock is provided with a function that will take an item of type T and apply an action to it. Here, the action is simply setting the HasReceivedRedCard property of the Player object to true. If the incoming object isn't a Player, it throws an exception.
The Post method feeds data into the WriteOnceBlock and prints the status of each operation. Each item is being posted to the WriteOnceBlock. If an item is accepted, it means this is the first item posted, and the block won't accept further items (because WriteOnceBlock, as the name suggests, accepts only a single item). If an item is rejected, it means an item has already been accepted previously. The status (accepted or rejected) and iteration count are logged to the console.
Do note that this sample accepts only a single item and cannot be reused after one successful insertion. In a production scenario, it's paramount to keep this point in mind when designing data flow.
using System.Threading.Tasks.Dataflow;
public class PlayerInfo
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = "Eric Cantona";
public bool HasReceivedRedCard { get; set; }
}
public interface IWriteOnceBlockService<T> where T : class
{
void Post(List<T> items);
}
public class DemoWriteOnceBlock<T> : IWriteOnceBlockService<T> where T : PlayerInfo, new()
{
private readonly WriteOnceBlock<T> _writeBlock;
public DemoWriteOnceBlock()
{
_writeBlock = new WriteOnceBlock<T>(DoAction);
}
public void Post(List<T> items)
{
var count = 1;
foreach (var player in items)
{
if (_writeBlock.Post(player))
{
Console.WriteLine($"Accepted: {count}, iteration: {player.Name}");
}
else
{
Console.WriteLine($"Rejected: {count}, iteration: {player.Name}");
}
count++;
}
// no matter how many times you will attempt,
// block will return only the first accepted value...
for (var i = 1; i < 10; i++)
{
Console.WriteLine(_writeBlock.TryReceive(out var p)
? $"Received: {p.Name} - HasReceivedRedCard: {p.HasReceivedRedCard}, iteration: {i}"
: $"Queue empty");
}
}
private T DoAction(T a)
{
a.HasReceivedRedCard = true;
Console.WriteLine($"The player {a.Name} received a red card: {a.HasReceivedRedCard}");
return a;
}
}
// test it in console
var writeOnceBlock = new DemoWriteOnceBlock<PlayerInfo>();
writeOnceBlock.Post([
new(),
new() { Name = "Player 2" },
new() { Name = "Player 3" },
new() { Name = "Player 4" },
new() { Name = "Player 5" },
new() { Name = "Player 7" }
]);
Files you can download: