C# event debounce - c#

I'm listening to a hardware event message, but I need to debounce it to avoid too many queries.
This is an hardware event that sends the machine status and I have to store it in a database for statistical purposes, and it sometimes happens that its status changes very often (flickering?). In this case I would like to store only a "stable" status and I want to implement it by simply waiting for 1-2s before storing the status to the database.
This is my code:
private MachineClass connect()
{
try
{
MachineClass rpc = new MachineClass();
rpc.RxVARxH += eventRxVARxH;
return rpc;
}
catch (Exception e1)
{
log.Error(e1.Message);
return null;
}
}
private void eventRxVARxH(MachineClass Machine)
{
log.Debug("Event fired");
}
I call this behaviour "debounce": wait a few times to really do its job: if the same event is fired again during the debounce time, I have to dismiss the first request and start to wait the debounce time to complete the second event.
What is the best choice to manage it? Simply a one-shot timer?
To explain the "debounce" function please see this javascript implementation for key events:
http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/

I've used this to debounce events with some success:
public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
{
var last = 0;
return arg =>
{
var current = Interlocked.Increment(ref last);
Task.Delay(milliseconds).ContinueWith(task =>
{
if (current == last) func(arg);
task.Dispose();
});
};
}
Usage
Action<int> a = (arg) =>
{
// This was successfully debounced...
Console.WriteLine(arg);
};
var debouncedWrapper = a.Debounce<int>();
while (true)
{
var rndVal = rnd.Next(400);
Thread.Sleep(rndVal);
debouncedWrapper(rndVal);
}
It may not be a robust as what's in RX but it's easy to understand and use.
Followup 2020-02-03
Revised #collie's solution using cancellation tokens as follows
public static Action<T> Debounce<T>(this Action<T> func, int milliseconds = 300)
{
CancellationTokenSource? cancelTokenSource = null;
return arg =>
{
cancelTokenSource?.Cancel();
cancelTokenSource = new CancellationTokenSource();
Task.Delay(milliseconds, cancelTokenSource.Token)
.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
{
func(arg);
}
}, TaskScheduler.Default);
};
}
Notes:
Calling Cancel is enough to dispose of the CTS
A successfully completed CTS is not canceled/disposed until the next call
As noted by #collie, tasks get disposed so no need to call Dispose on the task
I've not worked with cancellation tokens before and may not be using them correctly.

This isn't a trivial request to code from scratch as there are several nuances. A similar scenario is monitoring a FileSystemWatcher and waiting for things to quiet down after a big copy, before you try to open the modified files.
Reactive Extensions in .NET 4.5 were created to handle exactly these scenarios. You can use them easily to provide such functionality with methods like Throttle, Buffer, Window or Sample. You post the events to a Subject, apply one of the windowing functions to it, for example to get a notification only if there was no activity for X seconds or Y events, then subscribe to the notification.
Subject<MyEventData> _mySubject=new Subject<MyEventData>();
....
var eventSequenc=mySubject.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(events=>MySubscriptionMethod(events));
Throttle returns the last event in a sliding window, only if there were no other events in the window. Any event resets the window.
You can find a very good overview of the time-shifted functions here
When your code receives the event, you only need to post it to the Subject with OnNext:
_mySubject.OnNext(MyEventData);
If your hardware event surfaces as a typical .NET Event, you can bypass the Subject and manual posting with Observable.FromEventPattern, as shown here:
var mySequence = Observable.FromEventPattern<MyEventData>(
h => _myDevice.MyEvent += h,
h => _myDevice.MyEvent -= h);
_mySequence.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(events=>MySubscriptionMethod(events));
You can also create observables from Tasks, combine event sequences with LINQ operators to request eg: pairs of different hardware events with Zip, use another event source to bound Throttle/Buffer etc, add delays and a lot more.
Reactive Extensions is available as a NuGet package, so it's very easy to add them to your project.
Stephen Cleary's book "Concurrency in C# Cookbook" is a very good resource on Reactive Extensions among other things, and explains how you can use it and how it fits with the rest of the concurrent APIs in .NET like Tasks, Events etc.
Introduction to Rx is an excellent series of articles (that's where I copied the samples from), with several examples.
UPDATE
Using your specific example, you could do something like:
IObservable<MachineClass> _myObservable;
private MachineClass connect()
{
MachineClass rpc = new MachineClass();
_myObservable=Observable
.FromEventPattern<MachineClass>(
h=> rpc.RxVARxH += h,
h=> rpc.RxVARxH -= h)
.Throttle(TimeSpan.FromSeconds(1));
_myObservable.Subscribe(machine=>eventRxVARxH(machine));
return rpc;
}
This can be improved vastly of course - both the observable and the subscription need to be disposed at some point. This code assumes that you only control a single device. If you have many devices, you could create the observable inside the class so that each MachineClass exposes and disposes its own observable.

Recently I was doing some maintenance on an application that was targeting an older version of the .NET framework (v3.5).
I couldn't use Reactive Extensions nor Task Parallel Library, but I needed a nice, clean, consistent way of debouncing events. Here's what I came up with:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace MyApplication
{
public class Debouncer : IDisposable
{
readonly TimeSpan _ts;
readonly Action _action;
readonly HashSet<ManualResetEvent> _resets = new HashSet<ManualResetEvent>();
readonly object _mutex = new object();
public Debouncer(TimeSpan timespan, Action action)
{
_ts = timespan;
_action = action;
}
public void Invoke()
{
var thisReset = new ManualResetEvent(false);
lock (_mutex)
{
while (_resets.Count > 0)
{
var otherReset = _resets.First();
_resets.Remove(otherReset);
otherReset.Set();
}
_resets.Add(thisReset);
}
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
if (!thisReset.WaitOne(_ts))
{
_action();
}
}
finally
{
lock (_mutex)
{
using (thisReset)
_resets.Remove(thisReset);
}
}
});
}
public void Dispose()
{
lock (_mutex)
{
while (_resets.Count > 0)
{
var reset = _resets.First();
_resets.Remove(reset);
reset.Set();
}
}
}
}
}
Here's an example of using it in a windows form that has a search text box:
public partial class Example : Form
{
private readonly Debouncer _searchDebouncer;
public Example()
{
InitializeComponent();
_searchDebouncer = new Debouncer(TimeSpan.FromSeconds(.75), Search);
txtSearchText.TextChanged += txtSearchText_TextChanged;
}
private void txtSearchText_TextChanged(object sender, EventArgs e)
{
_searchDebouncer.Invoke();
}
private void Search()
{
if (InvokeRequired)
{
Invoke((Action)Search);
return;
}
if (!string.IsNullOrEmpty(txtSearchText.Text))
{
// Search here
}
}
}

I ran into issues with this. I tried each of the answers here, and since I'm in a Xamarin universal app, I seem to be missing certain things that are required in each of these answers, and I didn't want to add any more packages or libraries. My solution works exactly how I'd expect it to, and I haven't run into any issues with it. Hope it helps somebody.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace OrderScanner.Models
{
class Debouncer
{
private List<CancellationTokenSource> StepperCancelTokens = new List<CancellationTokenSource>();
private int MillisecondsToWait;
private readonly object _lockThis = new object(); // Use a locking object to prevent the debouncer to trigger again while the func is still running
public Debouncer(int millisecondsToWait = 300)
{
this.MillisecondsToWait = millisecondsToWait;
}
public void Debouce(Action func)
{
CancelAllStepperTokens(); // Cancel all api requests;
var newTokenSrc = new CancellationTokenSource();
lock (_lockThis)
{
StepperCancelTokens.Add(newTokenSrc);
}
Task.Delay(MillisecondsToWait, newTokenSrc.Token).ContinueWith(task => // Create new request
{
if (!newTokenSrc.IsCancellationRequested) // if it hasn't been cancelled
{
CancelAllStepperTokens(); // Cancel any that remain (there shouldn't be any)
StepperCancelTokens = new List<CancellationTokenSource>(); // set to new list
lock (_lockThis)
{
func(); // run
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private void CancelAllStepperTokens()
{
foreach (var token in StepperCancelTokens)
{
if (!token.IsCancellationRequested)
{
token.Cancel();
}
}
}
}
}
It's called like so...
private Debouncer StepperDeboucer = new Debouncer(1000); // one second
StepperDeboucer.Debouce(() => { WhateverMethod(args) });
I wouldn't recommend this for anything where the machine could be sending in hundreds of requests a second, but for user input, it works excellently. I'm using it on a stepper in an android/IOS app that calls to an api on step.

RX is probably the easiest choice, especially if you're already using it in your application. But if not, adding it might be a bit of overkill.
For UI based applications (like WPF) I use the following class that use DispatcherTimer:
public class DebounceDispatcher
{
private DispatcherTimer timer;
private DateTime timerStarted { get; set; } = DateTime.UtcNow.AddYears(-1);
public void Debounce(int interval, Action<object> action,
object param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher disp = null)
{
// kill pending timer and pending ticks
timer?.Stop();
timer = null;
if (disp == null)
disp = Dispatcher.CurrentDispatcher;
// timer is recreated for each event and effectively
// resets the timeout. Action only fires after timeout has fully
// elapsed without other events firing in between
timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
{
if (timer == null)
return;
timer?.Stop();
timer = null;
action.Invoke(param);
}, disp);
timer.Start();
}
}
To use it:
private DebounceDispatcher debounceTimer = new DebounceDispatcher();
private void TextSearchText_KeyUp(object sender, KeyEventArgs e)
{
debounceTimer.Debounce(500, parm =>
{
Model.AppModel.Window.ShowStatus("Searching topics...");
Model.TopicsFilter = TextSearchText.Text;
Model.AppModel.Window.ShowStatus();
});
}
Key events are now only processed after keyboard is idle for 200ms - any previous pending events are discarded.
There's also a Throttle method which always fires events after a given interval:
public void Throttle(int interval, Action<object> action,
object param = null,
DispatcherPriority priority = DispatcherPriority.ApplicationIdle,
Dispatcher disp = null)
{
// kill pending timer and pending ticks
timer?.Stop();
timer = null;
if (disp == null)
disp = Dispatcher.CurrentDispatcher;
var curTime = DateTime.UtcNow;
// if timeout is not up yet - adjust timeout to fire
// with potentially new Action parameters
if (curTime.Subtract(timerStarted).TotalMilliseconds < interval)
interval = (int) curTime.Subtract(timerStarted).TotalMilliseconds;
timer = new DispatcherTimer(TimeSpan.FromMilliseconds(interval), priority, (s, e) =>
{
if (timer == null)
return;
timer?.Stop();
timer = null;
action.Invoke(param);
}, disp);
timer.Start();
timerStarted = curTime;
}

Panagiotis's answer is certainly correct, however I wanted to give a simpler example, as it took me a while to sort through how to get it working. My scenario is that a user types in a search box, and as the user types we want to make api calls to return search suggestions, so we want to debounce the api calls so they don't make one every time they type a character.
I'm using Xamarin.Android, however this should apply to any C# scenario...
private Subject<string> typingSubject = new Subject<string> ();
private IDisposable typingEventSequence;
private void Init () {
var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
searchText.TextChanged += SearchTextChanged;
typingEventSequence = typingSubject.Throttle (TimeSpan.FromSeconds (1))
.Subscribe (query => suggestionsAdapter.Get (query));
}
private void SearchTextChanged (object sender, TextChangedEventArgs e) {
var searchText = layoutView.FindViewById<EditText> (Resource.Id.search_text);
typingSubject.OnNext (searchText.Text.Trim ());
}
public override void OnDestroy () {
if (typingEventSequence != null)
typingEventSequence.Dispose ();
base.OnDestroy ();
}
When you first initialize the screen / class, you create your event to listen to the user typing (SearchTextChanged), and then also set up a throttling subscription, which is tied to the "typingSubject".
Next, in your SearchTextChanged event, you can call typingSubject.OnNext and pass in the search box's text. After the debounce period (1 second), it will call the subscribed event (suggestionsAdapter.Get in our case.)
Lastly, when the screen is closed, make sure to dispose of the subscription!

This little gem is inspired by Mike Wards diabolically ingenious extension attempt. However, this one cleans up after itself quite nicely.
public static Action Debounce(this Action action, int milliseconds = 300)
{
CancellationTokenSource lastCToken = null;
return () =>
{
//Cancel/dispose previous
lastCToken?.Cancel();
try {
lastCToken?.Dispose();
} catch {}
var tokenSrc = lastCToken = new CancellationTokenSource();
Task.Delay(milliseconds).ContinueWith(task => { action(); }, tokenSrc.Token);
};
}
Note: there's no need to dispose of the task in this case. See here for the evidence.
Usage
Action DebounceToConsole;
int count = 0;
void Main()
{
//Assign
DebounceToConsole = ((Action)ToConsole).Debounce(50);
var random = new Random();
for (int i = 0; i < 50; i++)
{
DebounceToConsole();
Thread.Sleep(random.Next(100));
}
}
public void ToConsole()
{
Console.WriteLine($"I ran for the {++count} time.");
}

I needed something like this but in a web-application, so I can't store the Action in a variable, it will be lost between http requests.
Based on other answers and #Collie idea I created a class that looks at a unique string key for throttling.
public static class Debouncer
{
static ConcurrentDictionary<string, CancellationTokenSource> _tokens = new ConcurrentDictionary<string, CancellationTokenSource>();
public static void Debounce(string uniqueKey, Action action, int seconds)
{
var token = _tokens.AddOrUpdate(uniqueKey,
(key) => //key not found - create new
{
return new CancellationTokenSource();
},
(key, existingToken) => //key found - cancel task and recreate
{
existingToken.Cancel(); //cancel previous
return new CancellationTokenSource();
}
);
Task.Delay(seconds * 1000, token.Token).ContinueWith(task =>
{
if (!task.IsCanceled)
{
action();
_tokens.TryRemove(uniqueKey, out _);
}
}, token.Token);
}
}
Usage:
//throttle for 5 secs if it's already been called with this KEY
Debouncer.Debounce("Some-Unique-ID", () => SendEmails(), 5);
As a side bonus, because it's based on a string key, you can use inline lambda's
Debouncer.Debounce("Some-Unique-ID", () =>
{
//do some work here
}, 5);

Created this class for solving it also for awaitable calls:
public class Debouncer
{
private CancellationTokenSource _cancelTokenSource = null;
public async Task Debounce(Func<Task> method, int milliseconds = 300)
{
_cancelTokenSource?.Cancel();
_cancelTokenSource?.Dispose();
_cancelTokenSource = new CancellationTokenSource();
await Task.Delay(milliseconds, _cancelTokenSource.Token);
await method();
}
}
Sample of use:
private Debouncer _debouncer = new Debouncer();
....
await _debouncer.Debounce(YourAwaitableMethod);

This is inspired by Nieminen's Task.Delay-based Debouncer class. Simplified, some minor corrections, and should clean up after itself better.
class Debouncer: IDisposable
{
private CancellationTokenSource lastCToken;
private int milliseconds;
public Debouncer(int milliseconds = 300)
{
this.milliseconds = milliseconds;
}
public void Debounce(Action action)
{
Cancel(lastCToken);
var tokenSrc = lastCToken = new CancellationTokenSource();
Task.Delay(milliseconds).ContinueWith(task =>
{
action();
},
tokenSrc.Token
);
}
public void Cancel(CancellationTokenSource source)
{
if (source != null)
{
source.Cancel();
source.Dispose();
}
}
public void Dispose()
{
Cancel(lastCToken);
}
~Debouncer()
{
Dispose();
}
}
Usage
private Debouncer debouncer = new Debouncer(500); //1/2 a second
...
debouncer.Debounce(SomeAction);

I needed a Debounce method for Blazor and kept coming back to this page so I wanted to share my solution in case it helps others.
public class DebounceHelper
{
private CancellationTokenSource debounceToken = null;
public async Task DebounceAsync(Func<CancellationToken, Task> func, int milliseconds = 1000)
{
try
{
// Cancel previous task
if (debounceToken != null) { debounceToken.Cancel(); }
// Assign new token
debounceToken = new CancellationTokenSource();
// Debounce delay
await Task.Delay(milliseconds, debounceToken.Token);
// Throw if canceled
debounceToken.Token.ThrowIfCancellationRequested();
// Run function
await func(debounceToken.Token);
}
catch (TaskCanceledException) { }
}
}
Example call on a search function
<input type="text" #oninput=#(async (eventArgs) => await OnSearchInput(eventArgs)) />
#code {
private readonly DebounceHelper debouncer = new DebounceHelper();
private async Task OnSearchInput(ChangeEventArgs eventArgs)
{
await debouncer.DebounceAsync(async (cancellationToken) =>
{
// Search Code Here
});
}
}

Simply remember the latest 'hit:
DateTime latestHit = DatetIme.MinValue;
private void eventRxVARxH(MachineClass Machine)
{
log.Debug("Event fired");
if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
{
// ignore second hit, too fast
return;
}
latestHit = DateTime.Now;
// it was slow enough, do processing
...
}
This will allow a second event if there was enough time after the last event.
Please note: it is not possible (in a simple way) to handle the last event in a series of fast events, because you never know which one is the last...
...unless you are prepared to handle the last event of a burst which is a long time ago. Then you have to remember the last event and log it if the next event is slow enough:
DateTime latestHit = DatetIme.MinValue;
Machine historicEvent;
private void eventRxVARxH(MachineClass Machine)
{
log.Debug("Event fired");
if(latestHit - DateTime.Now < TimeSpan.FromXYZ() // too fast
{
// ignore second hit, too fast
historicEvent = Machine; // or some property
return;
}
latestHit = DateTime.Now;
// it was slow enough, do processing
...
// process historicEvent
...
historicEvent = Machine;
}

I did some more simple solution based on #Mike Ward answer:
public static class CustomTaskExtension
{
#region fields
private static int _last = 0;
#endregion
public static void Debounce(CancellationTokenSource throttleCts, double debounceTimeMs, Action action)
{
var current = Interlocked.Increment(ref _last);
Task.Delay(TimeSpan.FromMilliseconds(debounceTimeMs), throttleCts.Token)
.ContinueWith(task =>
{
if (current == _last) action();
task.Dispose();
});
}
}
Example how to use it:
// security way to cancel the debounce process any time
CancellationTokenSource _throttleCts = new CancellationTokenSource();
public void MethodCalledManyTimes()
{
// will wait 250ms after the last call
CustomTaskExtension.Debounce(_throttleCts, 250, async () =>
{
Console.Write("Execute your code 250ms after the last call.");
});
}

I came up with this in my class definition.
I wanted to run my action immediately if there hasn't been any action for the time period (3 seconds in the example).
If something has happened in the last three seconds, I want to send the last thing that happened within that time.
private Task _debounceTask = Task.CompletedTask;
private volatile Action _debounceAction;
/// <summary>
/// Debounces anything passed through this
/// function to happen at most every three seconds
/// </summary>
/// <param name="act">An action to run</param>
private async void DebounceAction(Action act)
{
_debounceAction = act;
await _debounceTask;
if (_debounceAction == act)
{
_debounceTask = Task.Delay(3000);
act();
}
}
So, if I have subdivide my clock into every quarter of a second
TIME: 1e&a2e&a3e&a4e&a5e&a6e&a7e&a8e&a9e&a0e&a
EVENT: A B C D E F
OBSERVED: A B E F
Note that no attempt is made to cancel the task early, so it's possible for actions to pile up for 3 seconds before eventually being available for garbage collection.

Figured out how to use System.Reactive NuGet package for doing a proper debouncing on a TextBox.
At the class level, we have our field
private IObservable<EventPattern<TextChangedEventArgs>> textChanged;
Then when we want to start listening to the event:
// Debouncing capability
textChanged = Observable.FromEventPattern<TextChangedEventArgs>(txtSearch, "TextChanged");
textChanged.ObserveOnDispatcher().Throttle(TimeSpan.FromSeconds(1)).Subscribe(args => {
Debug.WriteLine("bounce!");
});
Make sure you don't also wire your textbox up to an event handler. The Lambda above is the event handler.

I wrote an async debouncer that doesn't run async-in-sync.
public sealed class Debouncer : IDisposable {
public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);
private readonly TimeSpan _delay;
private CancellationTokenSource? previousCancellationToken = null;
public async Task Debounce(Action action) {
_ = action ?? throw new ArgumentNullException(nameof(action));
Cancel();
previousCancellationToken = new CancellationTokenSource();
try {
await Task.Delay(_delay, previousCancellationToken.Token);
await Task.Run(action, previousCancellationToken.Token);
}
catch (TaskCanceledException) { } // can swallow exception as nothing more to do if task cancelled
}
public void Cancel() {
if (previousCancellationToken != null) {
previousCancellationToken.Cancel();
previousCancellationToken.Dispose();
}
}
public void Dispose() => Cancel();
}
I use it to debounce changes reported on file changes, see complete example here.

I was inspired by Mike's answer, but needed solution that worked without tasks, which simply swallows subsequent event invocations until debounce time-out runs out. Here's my solution:
public static Action<T> Debounce<T>(this Action<T> action, int milliseconds = 300)
{
DateTime? runningCallTime = null;
var locker = new object();
return arg =>
{
lock (locker)
{
if (!runningCallTime.HasValue ||
runningCallTime.Value.AddMilliseconds(milliseconds) <= DateTime.UtcNow)
{
runningCallTime = DateTime.UtcNow;
action.Invoke(arg);
}
}
};
}

Another implementation
public static class Debounce
{
public static Action Action(Action action, TimeSpan time)
{
var timer = new Timer(_ => action(), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
return () => timer.Change(time, Timeout.InfiniteTimeSpan);
}
}

None of the above answers fully worked for me, so I've come up with the following implementation:
public class Debouncer
{
private CancellationTokenSource _cancelTokenSource = null;
public Task Debounce(Func<Task> method, int milliseconds = 250)
{
_cancelTokenSource?.Cancel();
_cancelTokenSource?.Dispose();
_cancelTokenSource = new CancellationTokenSource();
try
{
return Task.Delay(milliseconds, _cancelTokenSource.Token)
.ContinueWith(_ => method(), _cancelTokenSource.Token);
}
catch (TaskCanceledException exception) when (exception.CancellationToken == _cancelTokenSource.Token)
{
}
return Task.CompletedTask;
}
}
Usage:
var debouncer = new Debouncer();
await debouncer.Debounce(async () => await someAction());

I know I'm a couple hundred thousand minutes late to this party but I figured I'd add my 2 cents. I'm surprised no one has suggested this so I'm assuming there's something I don't know that might make it less than ideal so maybe I'll learn something new if this gets shot down.
I often use a solution that uses the System.Threading.Timer's Change() method.
using System.Threading;
Timer delayedActionTimer;
public MyClass()
{
// Setup our timer
delayedActionTimer = new Timer(saveOrWhatever, // The method to call when triggered
null, // State object (Not required)
Timeout.Infinite, // Start disabled
Timeout.Infinite); // Don't repeat the trigger
}
// A change was made that we want to save but not until a
// reasonable amount of time between changes has gone by
// so that we're not saving on every keystroke/trigger event.
public void TextChanged()
{
delayedActionTimer.Change(3000, // Trigger this timers function in 3 seconds,
// overwriting any existing countdown
Timeout.Infinite); // Don't repeat this trigger; Only fire once
}
// Timer requires the method take an Object which we've set to null since we don't
// need it for this example
private void saveOrWhatever(Object obj)
{
/*Do the thing*/
}

Related

C# Immutable counter for multiple fields

I have a fairly high throughput on a message counter (tens of thousands per second), and looking for an efficient way of getting the count without putting locks everywhere or ideally not locking on each message count when i am giving an update every 10 seconds.
Use of immutable counter object
I am using an immutable counter class:
public class Counter
{
public Counter(int quotes, int trades)
{
Quotes = quotes;
Trades = trades;
}
readonly public int Quotes;
readonly public int Trades;
// and some other counter fields snipped
}
And would update this on each message process loop:
class MyProcessor
{
System.Timers.Timer timer;
Counter counter = new Counter(0,0);
public MyProcessor()
{
// update ever 10 seconds
this.timer = new System.Timers.Timer(10000);
timer.Elapsed += (sender, e) => {
var quotesPerSecond = this.counter.Quotes / 10.0;
var tradesPerSecond = this.counter.Trades / 10.0;
this.Counter = new Counter(0,0);
});
}
public void ProcessMessages(Messages messages)
{
foreach(var message in messages) { /* */ }
var oldCounter = counter;
this.counter = new Counter(oldCounter.Quotes, oldCounter.Trades);
}
}
I have lots of counters (not all shown), so would mean a lot of individual Interlocked.Increment calls on individual counter fields.
The only other way I can think of is lock every single run of ProcessMessages (which will be extensive) and heavy for something which is a utility as opposed to critical where the program would crash.
Is it possible to use an immutable counter object in this fashion without hard interlocking/thread mechanisms when we only need to update once every 10 seconds?
Flag check idea to avoid locks
Could the timer thread set a flag for the ProcessMessages to check and if it sees it set, start the count from zero again, i.e.
/* snipped the MyProcessor class, same as before */
System.Timers.Timer timer;
Counter counter = new Counter(0,0);
ManualResetEvent reset = new ManualResetEvent(false);
public MyProcessor()
{
// update ever 10 seconds
this.timer = new System.Timers.Timer(10000);
timer.Elapsed += (sender, e) => {
var quotesPerSecond = this.counter.Quotes / 10.0;
var tradesPerSecond = this.counter.Trades / 10.0;
// log
this.reset.Set();
});
}
// this should be called every second with a heartbeat message posted to queue
public void ProcessMessages(Messages messages)
{
if (reset.WaitOne(0) == true)
{
this.counter = new Counter(this.counter.Quotes, this.counter.Trades, this.counter.Aggregates);
reset.Reset();
}
else
{
this.counter = new Counter(
this.counter.Quotes + message.Quotes.Count,
this.counter.Trades + message.Trades.Count);
}
}
/* end of MyProcessor class */
This would work, however the update "stalls" when the process messages comes to a halt (although the throughput is very high, it does pause for a number of hours at night ideally should show the actual rather than last value).
One way around this would be to post a heartbeat message to the MyProcessor.ProcessMessages() every second to force an internal update of the message counters and subsequent reset when the reset ManualResetEvent is set.
Here are three new methods for your Counter class. One for reading the latest value from a specific location, one for updating safely a specific location, and one for creating easily a new Counter based on an existing one:
public static Counter Read(ref Counter counter)
{
return Interlocked.CompareExchange(ref counter, null, null);
}
public static void Update(ref Counter counter, Func<Counter, Counter> updateFactory)
{
var counter1 = counter;
while (true)
{
var newCounter = updateFactory(counter1);
var counter2 = Interlocked.CompareExchange(ref counter, newCounter, counter1);
if (counter2 == counter1) break;
counter1 = counter2;
}
}
public Counter Add(int quotesDelta, int tradesDelta)
{
return new Counter(Quotes + quotesDelta, Trades + tradesDelta);
}
Usage example:
Counter latest = Counter.Read(ref this.counter);
Counter.Update(ref this.counter, existing => existing.Add(1, 1));
Accessing the MyProcessor.counter field directly by multiple threads concurrently is not thread-safe, because it's neither volatile nor protected by a lock. The above methods are safe to use because they are accessing the field through interlocked operations.
I wanted to update everyone with what I had come up with, the counter updates were pushed within the thread itself.
Everything is driven by the DequeueThread loop, and specifically this.queue.ReceiveAsync(TimeSpan.FromSeconds(UpdateFrequencySeconds)) function.
This will either return an item from the queue, process it and update the counters, or timeout and then update the counters - there are no other threads involved everything, including updating message rate, is done within the thread.
In summary, nothing runs in parallel (in terms of dequing the packet), it is fetching the items one at a time and processing it and the counters thereafter. Then finally looping back to process the next item in the queue.
This removes the need for synchronisation:
internal class Counter
{
public Counter(Action<int,int,int,int> updateCallback, double updateEvery)
{
this.updateCallback = updateCallback;
this.UpdateEvery = updateEvery;
}
public void Poll()
{
if (nextUpdate < DateTimeOffset.UtcNow)
{
// post the stats, and reset
this.updateCallback(this.quotes, this.trades, this.aggregates, this.statuses);
this.quotes = 0;
this.trades = 0;
this.aggregates = 0;
this.statuses = 0;
nextUpdate = DateTimeOffset.UtcNow.AddSeconds(this.UpdateEvery);
}
}
public void AddQuotes(int count) => this.quotes += count;
public void AddTrades(int count) => this.trades += count;
public void AddAggregates(int count) => this.aggregates += count;
public void AddStatuses(int count) => this.statuses += count;
private int quotes;
private int trades;
private int aggregates;
private int statuses;
private readonly Action<int,int,int,int> updateCallback;
public double UpdateEvery { get; private set; }
private DateTimeOffset nextUpdate;
}
public class DeserializeWorker
{
private readonly BufferBlock<byte[]> queue = new BufferBlock<byte[]>();
private readonly IPolygonDeserializer polygonDeserializer;
private readonly ILogger<DeserializeWorker> logger;
private readonly Counter counter;
const double UpdateFrequencySeconds = 5.0;
long maxBacklog = 0;
public DeserializeWorker(IPolygonDeserializer polygonDeserializer, ILogger<DeserializeWorker> logger)
{
this.polygonDeserializer = polygonDeserializer ?? throw new ArgumentNullException(nameof(polygonDeserializer));
this.logger = logger;
this.counter = new Counter(ProcesCounterUpdateCallback, UpdateFrequencySeconds);
}
public void Add(byte[] data)
{
this.queue.Post(data);
}
public Task Run(CancellationToken stoppingToken)
{
return Task
.Factory
.StartNew(
async () => await DequeueThread(stoppingToken),
stoppingToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Default)
.Unwrap();
}
private async Task DequeueThread(CancellationToken stoppingToken)
{
while (stoppingToken.IsCancellationRequested == false)
{
try
{
var item = await this.queue.ReceiveAsync(TimeSpan.FromSeconds(UpdateFrequencySeconds), stoppingToken);
await ProcessAsync(item);
}
catch (TimeoutException)
{
// this is ok, timeout expired
}
catch(TaskCanceledException)
{
break; // task cancelled, break from loop
}
catch (Exception e)
{
this.logger.LogError(e.ToString());
}
UpdateCounters();
}
await StopAsync();
}
protected async Task StopAsync()
{
this.queue.Complete();
await this.queue.Completion;
}
protected void ProcessStatuses(IEnumerable<Status> statuses)
{
Parallel.ForEach(statuses, (current) =>
{
if (current.Result != "success")
this.logger.LogInformation($"{current.Result}: {current.Message}");
});
}
protected void ProcessMessages<T>(IEnumerable<T> messages)
{
Parallel.ForEach(messages, (current) =>
{
// serialize by type T
// dispatch
});
}
async Task ProcessAsync(byte[] item)
{
try
{
var memoryStream = new MemoryStream(item);
var message = await this.polygonDeserializer.DeserializeAsync(memoryStream);
var messagesTask = Task.Run(() => ProcessStatuses(message.Statuses));
var quotesTask = Task.Run(() => ProcessMessages(message.Quotes));
var tradesTask = Task.Run(() => ProcessMessages(message.Trades));
var aggregatesTask = Task.Run(() => ProcessMessages(message.Aggregates));
this.counter.AddStatuses(message.Statuses.Count);
this.counter.AddQuotes(message.Quotes.Count);
this.counter.AddTrades(message.Trades.Count);
this.counter.AddAggregates(message.Aggregates.Count);
Task.WaitAll(messagesTask, quotesTask, aggregatesTask, tradesTask);
}
catch (Exception e)
{
this.logger.LogError(e.ToString());
}
}
void UpdateCounters()
{
var currentCount = this.queue.Count;
if (currentCount > this.maxBacklog)
this.maxBacklog = currentCount;
this.counter.Poll();
}
void ProcesCounterUpdateCallback(int quotes, int trades, int aggregates, int statuses)
{
var updateFrequency = this.counter.UpdateEvery;
logger.LogInformation(
$"Queue current {this.queue.Count} (max {this.maxBacklog }), {quotes / updateFrequency} quotes/sec, {trades / updateFrequency} trades/sec, {aggregates / updateFrequency} aggregates/sec, {statuses / updateFrequency} status/sec");
}
}

Threads monitoring a Queue<Actions>

I doing a small project to map a network (routers only) using SNMP. In order to speed things up, I´m trying to have a pool of threads responsible for doing the jobs I need, apart from the first job which is done by the main thread.
At this time I have two jobs, one takes a parameter the other doesn´t:
UpdateDeviceInfo(NetworkDevice nd)
UpdateLinks() *not defined yet
What I´m trying to achieve is to have those working threads waiting for a job to
appear on a Queue<Action> and wait while it is empty. The main thread will add the first job and then wait for all workers, which might add more jobs, to finish before starting adding the second job and wake up the sleeping threads.
My problem/questions are:
How to define the Queue<Actions> so that I can insert the methods and the parameters if any. If not possible I could make all functions accept the same parameter.
How to launch the working threads indefinitely. I not sure where should I create the for(;;).
This is my code so far:
public enum DatabaseState
{
Empty = 0,
Learning = 1,
Updating = 2,
Stable = 3,
Exiting = 4
};
public class NetworkDB
{
public Dictionary<string, NetworkDevice> database;
private Queue<Action<NetworkDevice>> jobs;
private string _community;
private string _ipaddress;
private Object _statelock = new Object();
private DatabaseState _state = DatabaseState.Empty;
private readonly int workers = 4;
private Object _threadswaitinglock = new Object();
private int _threadswaiting = 0;
public Dictionary<string, NetworkDevice> Database { get => database; set => database = value; }
public NetworkDB(string community, string ipaddress)
{
_community = community;
_ipaddress = ipaddress;
database = new Dictionary<string, NetworkDevice>();
jobs = new Queue<Action<NetworkDevice>>();
}
public void Start()
{
NetworkDevice nd = SNMP.GetDeviceInfo(new IpAddress(_ipaddress), _community);
if (nd.Status > NetworkDeviceStatus.Unknown)
{
database.Add(nd.Id, nd);
_state = DatabaseState.Learning;
nd.Update(this); // The first job is done by the main thread
for (int i = 0; i < workers; i++)
{
Thread t = new Thread(JobRemove);
t.Start();
}
lock (_statelock)
{
if (_state == DatabaseState.Learning)
{
Monitor.Wait(_statelock);
}
}
lock (_statelock)
{
if (_state == DatabaseState.Updating)
{
Monitor.Wait(_statelock);
}
}
foreach (KeyValuePair<string, NetworkDevice> n in database)
{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(n.Value.Name + ".txt")
{
file.WriteLine(n);
}
}
}
}
public void JobInsert(Action<NetworkDevice> func, NetworkDevice nd)
{
lock (jobs)
{
jobs.Enqueue(item);
if (jobs.Count == 1)
{
// wake up any blocked dequeue
Monitor.Pulse(jobs);
}
}
}
public void JobRemove()
{
Action<NetworkDevice> item;
lock (jobs)
{
while (jobs.Count == 0)
{
lock (_threadswaitinglock)
{
_threadswaiting += 1;
if (_threadswaiting == workers)
Monitor.Pulse(_statelock);
}
Monitor.Wait(jobs);
}
lock (_threadswaitinglock)
{
_threadswaiting -= 1;
}
item = jobs.Dequeue();
item.Invoke();
}
}
public bool NetworkDeviceExists(NetworkDevice nd)
{
try
{
Monitor.Enter(database);
if (database.ContainsKey(nd.Id))
{
return true;
}
else
{
database.Add(nd.Id, nd);
Action<NetworkDevice> action = new Action<NetworkDevice>(UpdateDeviceInfo);
jobs.Enqueue(action);
return false;
}
}
finally
{
Monitor.Exit(database);
}
}
//Job1 - Learning -> Update device info
public void UpdateDeviceInfo(NetworkDevice nd)
{
nd.Update(this);
try
{
Monitor.Enter(database);
nd.Status = NetworkDeviceStatus.Self;
}
finally
{
Monitor.Exit(database);
}
}
//Job2 - Updating -> After Learning, create links between neighbours
private void UpdateLinks()
{
}
}
Your best bet seems like using a BlockingCollection instead of the Queue class. They behave effectively the same in terms of FIFO, but a BlockingCollection will let each of your threads block until an item can be taken by calling GetConsumingEnumerable or Take. Here is a complete example.
http://mikehadlow.blogspot.com/2012/11/using-blockingcollection-to-communicate.html?m=1
As for including the parameters, it seems like you could use closure to enclose the NetworkDevice itself and then just enqueue Action instead of Action<>

C# Multi-threading, wait for all task to complete in a situation when new tasks are being constantly added

I have a situation where new tasks are being constantly generated and added to a ConcurrentBag<Tasks>.
I need to wait all tasks to complete.
Waiting for all the tasks in the ConcurrentBag via WaitAll is not enough as the number of tasks would have grown while the previous wait is completed.
At the moment I am waiting it in the following way:
private void WaitAllTasks()
{
while (true)
{
int countAtStart = _tasks.Count();
Task.WaitAll(_tasks.ToArray());
int countAtEnd = _tasks.Count();
if (countAtStart == countAtEnd)
{
break;
}
#if DEBUG
if (_tasks.Count() > 100)
{
tokenSource.Cancel();
break;
}
#endif
}
}
I am not very happy with the while(true) solution.
Can anyone suggest a better more efficient way to do this (without having to pool the processor constantly with a while(true))
Additional context information as requested in the comments. I don't think though this is relevant to the question.
This piece of code is used in a web crawler. The crawler scans page content and looks for two type of information. Data Pages and Link Pages. Data pages will be scanned and data will be collected, Link Pages will be scanned and more links will be collected from them.
As each of the tasks carry-on the activities and find more links, they add the links to an EventList. There is an event OnAdd on the list (code below) that is used to trigger other task to scan the newly added URLs. And so forth.
The job is complete when there are no more running tasks (so no more links will be added) and all items have been processed.
public IEventList<ISearchStatus> CurrentLinks { get; private set; }
public IEventList<IDataStatus> CurrentData { get; private set; }
public IEventList<System.Dynamic.ExpandoObject> ResultData { get; set; }
private readonly ConcurrentBag<Task> _tasks = new ConcurrentBag<Task>();
private readonly CancellationTokenSource tokenSource = new CancellationTokenSource();
private readonly CancellationToken token;
public void Search(ISearchDefinition search)
{
CurrentLinks.OnAdd += UrlAdded;
CurrentData.OnAdd += DataUrlAdded;
var status = new SearchStatus(search);
CurrentLinks.Add(status);
WaitAllTasks();
_exporter.Export(ResultData as IList<System.Dynamic.ExpandoObject>);
}
private void DataUrlAdded(object o, EventArgs e)
{
var item = o as IDataStatus;
if (item == null)
{
return;
}
_tasks.Add(Task.Factory.StartNew(() => ProcessObjectSearch(item), token));
}
private void UrlAdded(object o, EventArgs e)
{
var item = o as ISearchStatus;
if (item==null)
{
return;
}
_tasks.Add(Task.Factory.StartNew(() => ProcessFollow(item), token));
_tasks.Add(Task.Factory.StartNew(() => ProcessData(item), token));
}
public class EventList<T> : List<T>, IEventList<T>
{
public EventHandler OnAdd { get; set; }
private readonly object locker = new object();
public new void Add(T item)
{
//lock (locker)
{
base.Add(item);
}
OnAdd?.Invoke(item, null);
}
public new bool Contains(T item)
{
//lock (locker)
{
return base.Contains(item);
}
}
}
I think that this task can be done with TPL Dataflow library with very basic setup. You'll need a TransformManyBlock<Task, IEnumerable<DataTask>> and an ActionBlock (may be more of them) for actual data processing, like this:
// queue for a new urls to parse
var buffer = new BufferBlock<ParseTask>();
// parser itself, returns many data tasks from one url
// similar to LINQ.SelectMany method
var transform = new TransformManyBlock<ParseTask, DataTask>(task =>
{
// get all the additional urls to parse
var parsedLinks = GetLinkTasks(task);
// get all the data to parse
var parsedData = GetDataTasks(task);
// setup additional links to be parsed
foreach (var parsedLink in parsedLinks)
{
buffer.Post(parsedLink);
}
// return all the data to be processed
return parsedData;
});
// actual data processing
var consumer = new ActionBlock<DataTask>(s => ProcessData(s));
After that you need to link the blocks between each over:
buffer.LinkTo(transform, new DataflowLinkOptions { PropagateCompletion = true });
transform.LinkTo(consumer, new DataflowLinkOptions { PropagateCompletion = true });
Now you have a nice pipeline which will execute in background. At the moment you realize that everything you need is parsed, you simply call the Complete method for a block so it stops accepting news messages. After the buffer became empty, it will propagate the completion down the pipeline to transform block, which will propagate it down to consumer(s), and you need to wait for Completion task:
// no additional links would be accepted
buffer.Complete();
// after all the tasks are done, this will get fired
await consumer.Completion;
You can check the moment for a completion, for example, if both buffer' Count property and transform' InputCount and transform' CurrentDegreeOfParallelism (this is internal property for the TransformManyBlock) are equal to 0.
However, I suggested you to implement some additional logic here to determine current transformers number, as using the internal logic isn't a great solution. As for cancelling the pipeline, you can create a TPL block with a CancellationToken, either the one for all, or a dedicated for each block, getting the cancellation out of box.
Why not write one function that yields your tasks as necessary, when they are created? This way you can just use Task.WhenAll to wait for them to complete or, have I missed the point? See this working here.
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
try
{
Task.WhenAll(GetLazilyGeneratedSequenceOfTasks()).Wait();
Console.WriteLine("Fisnished.");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
public static IEnumerable<Task> GetLazilyGeneratedSequenceOfTasks()
{
var random = new Random();
var finished = false;
while (!finished)
{
var n = random.Next(1, 2001);
if (n < 50)
{
finished = true;
}
if (n > 499)
{
yield return Task.Delay(n);
}
Task.Delay(20).Wait();
}
yield break;
}
}
Alternatively, if your question is not as trivial as my answer may suggest, I'd consider a mesh with TPL Dataflow. The combination of a BufferBlock and an ActionBlock would get you very close to what you need. You could start here.
Either way, I'd suggest you want to include a provision for accepting a CancellationToken or two.

Observable.Interval restarts for each subscription

My first attempt to define a measurement schedule was:
var schedule = Observable.Concat(
Observable.Interval(TimeSpan.FromSeconds(1)).Take(3),
Observable.Interval(TimeSpan.FromSeconds(3)).Take(3),
Observable.Interval(TimeSpan.FromSeconds(5)));
Unfortunately, it restarts when unsubscribed and resubscribed, which is not a desired behavior in my case. Therefore, I came with something like this:
class Schedule : IObservable<DateTime>, IDisposable
{
readonly ISubject<DateTime> _subject;
readonly IDisposable _subscrption;
public Schedule()
{
_subject = new BehaviorSubject<DateTime>(DateTime.UtcNow);
_subscrption = Observable.Concat(
Observable.Interval(TimeSpan.FromSeconds(1)).Take(3),
Observable.Interval(TimeSpan.FromSeconds(3)).Take(3),
Observable.Interval(TimeSpan.FromSeconds(5)))
.Select(i => DateTime.UtcNow)
.Subscribe(_subject);
}
public IDisposable Subscribe(IObserver<DateTime> observer)
{
return _subject.Subscribe(observer);
}
public void Dispose()
{
_subscrption.Dispose();
}
}
It works but requires disposing after use. Are there any simple way to define Schedule without exposing IDisposable?
If you will be unsubscribing all of your observers and then resubscribing, there really is no way around keeping a Disposable around for tracking the connection. Since your Observable would not have anyway of directly knowing if the latest unsubscribe is really the last one.
I would recommend the use of Generate instead of Concatenating Observables
IConnectableObservable<DateTime> source = Observable.Generate(
0,
_ => true,
x => x + 1,
x => DateTime.UtcNow,
x => {
if (x < 3) return TimeSpan.FromSeconds(1);
else if (x < 5) return TimeSpan.FromSeconds(3);
else return TimeSpan.FromSeconds(5);
}).Publish();
Publish will turn your Observable into a ConnectableObservable, and gives you two options for managing when the source actually produces events.
//1) Explicit connect
IDisposable connection = source.Connect();
//2) RefCounted connection
IObservable<DateTime> rcSource = source.RefCount();
In the first version the source will become "hot" once you connect, and when you want to disconnect you should dispose of connection.
In the second the source will automatically disconnect when it has no more observers.
see also hot vs cold observables
Here is the solution I defined which does not require disposing. It is pretty complex though, so I would highly appreciate if something can be simplified using Rx instrumentation.
The usage:
IObservable<DateTime> schedule = new Schedule()
.Setup(TimeSpan.FromSeconds(1), 3)
.Setup(TimeSpan.FromSeconds(3), 3)
.Setup(TimeSpan.FromSeconds(5));
Where Schedule is:
class Schedule : IObservable<DateTime>
{
readonly TimeSpan TimerPrecision = TimeSpan.FromMilliseconds(1);
readonly IEnumerable<TimeSpan> Intervals;
readonly IEnumerator<DateTime> Event;
public Schedule()
: this(new TimeSpan[0])
{
}
Schedule(IEnumerable<TimeSpan> intervals)
{
Intervals = intervals;
Event = Start();
Event.MoveNext();
}
public Schedule Setup(TimeSpan interval)
{
return Setup(interval, Int32.MaxValue);
}
public Schedule Setup(TimeSpan interval, int repeat)
{
return new Schedule(
Intervals.Concat(
Enumerable.Repeat(interval, repeat)));
}
public IDisposable Subscribe(IObserver<DateTime> observer)
{
var timer = new System.Timers.Timer() { AutoReset = true };
timer.Elapsed += (s, a) =>
{
observer.OnNext(DateTime.UtcNow);
if (!TryArm(timer))
observer.OnCompleted();
};
if (!TryArm(timer))
observer.OnCompleted();
return timer;
}
IEnumerator<DateTime> Start()
{
var time = DateTime.UtcNow;
yield return time;
foreach (var interval in Intervals)
{
time += interval;
yield return time;
}
}
TimeSpan Delay()
{
var now = DateTime.UtcNow;
lock (Event)
while (Event.Current - DateTime.UtcNow < TimerPrecision)
Event.MoveNext();
return Event.Current - now;
}
bool TryArm(System.Timers.Timer timer)
{
try
{
timer.Interval = Delay().TotalMilliseconds;
timer.Start();
return true;
}
catch(ObjectDisposedException)
{
return false;
}
catch(InvalidOperationException)
{
return false;
}
}
}

Saving subscription state for resuming it later

Update - solved
The final solution differs a bit from Brandon's suggestion but his answer brought me on the right track.
class State
{
public int Offset { get; set; }
public HashSet<string> UniqueImageUrls = new HashSet<string>();
}
public IObservable<TPicture> GetPictures(ref object _state)
{
var localState = (State) _state ?? new State();
_state = localState;
return Observable.Defer(()=>
{
return Observable.Defer(() => Observable.Return(GetPage(localState.Offset)))
.SubscribeOn(TaskPoolScheduler.Default)
.Do(x=> localState.Offset += 20)
.Repeat()
.TakeWhile(x=> x.Count > 0)
.SelectMany(x=> x)
.Where(x=> !localState.UniqueImageUrls.Contains(x.ImageUrl))
.Do(x=> localState.UniqueImageUrls.Add(x.ImageUrl));
});
}
IList<TPicture> GetPage(int offset)
{
...
return result;
}
Original Question
I'm currently struggling with the following problem. The PictureProvider implementation shown below is working with an offset variable used for paging results of a backend service providing the actual data. What I would like to implement is an elegant solution making the current offset available to the consumer of the observable to allow for resuming the observable sequence at a later time at the correct offset. Resuming is already accounted for by the intialState argument to GetPictures().
Recommendations for improving the code in a more RX like fashion would be welcome as well. I'm actually not so sure if the Task.Run() stuff is appropriate here.
public class PictureProvider :
IPictureProvider<Picture>
{
#region IPictureProvider implementation
public IObservable<Picture> GetPictures(object initialState)
{
return Observable.Create<Picture>((IObserver<Picture> observer) =>
{
var state = new ProducerState(initialState);
ProducePictures(observer, state);
return state;
});
}
#endregion
void ProducePictures(IObserver<Picture> observer, ProducerState state)
{
Task.Run(() =>
{
try
{
while(!state.Terminate.WaitOne(0))
{
var page = GetPage(state.Offset);
if(page.Count == 0)
{
observer.OnCompleted();
break;
}
else
{
foreach(var picture in page)
observer.OnNext(picture);
state.Offset += page.Count;
}
}
}
catch (Exception ex)
{
observer.OnError(ex);
}
state.TerminateAck.Set();
});
}
IList<Picture> GetPage(int offset)
{
var result = new List<Picture>();
... boring web service call here
return result;
}
public class ProducerState :
IDisposable
{
public ProducerState(object initialState)
{
Terminate = new ManualResetEvent(false);
TerminateAck = new ManualResetEvent(false);
if(initialState != null)
Offset = (int) initialState;
}
public ManualResetEvent Terminate { get; private set; }
public ManualResetEvent TerminateAck { get; private set; }
public int Offset { get; set; }
#region IDisposable implementation
public void Dispose()
{
Terminate.Set();
TerminateAck.WaitOne();
Terminate.Dispose();
TerminateAck.Dispose();
}
#endregion
}
}
I suggest refactoring your interface to yield the state as part of the data. Now the client has what they need to resubscribe where they left off.
Also, once you start using Rx, you should find that using synchronization primitives like ManualResetEvent are rarely necessary. If you refactor your code so that retrieving each page is its own Task, then you can eliminate all of that synchronization code.
Also, if you are calling a "boring web service" in GetPage, then just make it async. This gets rid of the need to call Task.Run among other benefits.
Here is a refactored version, using .NET 4.5 async/await syntax. It could also be done without async/await. I also added a GetPageAsync method that uses Observable.Run just in case you really cannot convert your webservice call to be asynchronous
/// <summary>A set of pictures</summary>
public struct PictureSet
{
public int Offset { get; private set; }
public IList<Picture> Pictures { get; private set; }
/// <summary>Clients will use this property if they want to pick up where they left off</summary>
public int NextOffset { get { return Offset + Pictures.Count; } }
public PictureSet(int offset, IList<Picture> pictures)
:this() { Offset = offset; Pictures = pictures; }
}
public class PictureProvider : IPictureProvider<PictureSet>
{
public IObservable<PictureSet> GetPictures(int offset = 0)
{
// use Defer() so we can capture a copy of offset
// for each observer that subscribes (so multiple
// observers do not update each other's offset
return Observable.Defer<PictureSet>(() =>
{
var localOffset = offset;
// Use Defer so we re-execute GetPageAsync()
// each time through the loop.
// Update localOffset after each GetPageAsync()
// completes so that the next call to GetPageAsync()
// uses the next offset
return Observable.Defer(() => GetPageAsync(localOffset))
.Select(pictures =>
{
var s = new PictureSet(localOffset, pictures);
localOffset += pictures.Count;
})
.Repeat()
.TakeWhile(pictureSet => pictureSet.Pictures.Count > 0);
});
}
private async Task<IList<Picture>> GetPageAsync(int offset)
{
var data = await BoringWebServiceCallAsync(offset);
result = data.Pictures.ToList();
}
// this version uses Observable.Run() (which just uses Task.Run under the hood)
// in case you cannot convert your
// web service call to be asynchronous
private IObservable<IList<Picture>> GetPageAsync(int offset)
{
return Observable.Run(() =>
{
var result = new List<Picture>();
... boring web service call here
return result;
});
}
}
Clients just need to add a SelectMany call to get their IObservable<Picture>. They can choose to store the pictureSet.NextOffset if they wish.
pictureProvider
.GetPictures()
.SelectMany(pictureSet => pictureSet.Pictures)
.Subscribe(picture => whatever);
Instead of thinking about how to save the subscription state, I would think about how to replay the state of the inputs (i.e. I'd try to create a serializable ReplaySubject that, on resume, would just resubscribe and catch back up to the current state).

Categories