I've been using TPL DataFlow to control back pressure and have used it to solve this problem.
The key part is ITargetBlock<TInput>.AsObserver()
- source.
// Set a block to handle each element
ITargetBlock<long> targetBlock = new ActionBlock<long>(async p =>
{
Console.WriteLine($"Received {p}");
await Task.Delay(1000);
Console.WriteLine($"Finished handling {p}");
},
new ExecutionDataflowBlockOptions { BoundedCapacity = 1 });
// Generate an item each second for 10 seconds
var sequence = Observable.Interval(TimeSpan.FromSeconds(1)).Take(10);
// Subscribe with an observer created from the target block.
sequence.Subscribe(targetBlock.AsObserver());
// Await completion of the block
await targetBlock.Completion;
The important part here is that the ActionBlock's bounded capacity is set to 1. This prevents the block from receiving more than one item at a time and will block OnNext if an item is already being processed!
My big surprise here was that it can be safe to call Task.Wait
and Task.Result
inside your subscription. Obviously, if you have called ObserverOnDispatcher()
or similar you will probably hit deadlocks. Be careful!