I want to run periodic tasks in with a restriction that at most only one execution of a method is running at any given time.
I was experimenting with Rx, but I am no
Below are two implementations of a PeriodicSequentialExecution method, that creates an observable by executing an asynchronous method in a periodic fashion. The interval between subsequent executions can be extended to prevent overlapping, in which case the period is time-shifted accordingly. The first implementation is purely functional, while the second implementation is mostly imperative. Both implementations are functionally identical. The second one may be slightly more efficient.
The functional implementation:
/// <summary>
/// Creates an observable sequence containing the results of an asynchronous
/// action that is invoked periodically and sequentially (without overlapping).
/// </summary>
public static IObservable<T> PeriodicSequentialExecution<T>(
Func<CancellationToken, Task<T>> action,
TimeSpan dueTime, TimeSpan period)
{
return Delay(dueTime) // Initial delay
.Concat(
// Execution loop
Observable.Publish( // Start a hot (published) delay timer
Delay(period), hotTimer => Observable
.StartAsync(ct => action(ct)) // Start the operation
.Concat(hotTimer) // Await the delay timer
)
.Repeat()
);
static IObservable<T> Delay(TimeSpan delay)
=> Observable.Timer(delay).IgnoreElements().Select(_ => default(T));
}
The imperative implementation:
public static IObservable<T> PeriodicSequentialExecution<T>(
Func<CancellationToken, Task<T>> action,
TimeSpan dueTime, TimeSpan period)
{
return Observable.Create<T>(async (observer, cancellationToken) =>
{
try
{
await Task.Delay(dueTime, cancellationToken);
while (true)
{
var delayTask = Task.Delay(period, cancellationToken);
var result = await action(cancellationToken);
observer.OnNext(result);
await delayTask;
}
}
catch (Exception ex) { observer.OnError(ex); }
});
}
You are on the right track, you can use Select + Concat to flatten out the observable and limit the number of inflight requests (Note: if your task takes longer than the interval time, then they will start to stack up since they can't execute fast enough):
var source = Observable.Interval(TimeSpan.FromMilliseconds(100))
//I assume you are doing async work since you want to limit concurrency
.Select(_ => Observable.FromAsync(() => DoSomethingAsync()))
//This is equivalent to calling Merge(1)
.Concat();
source.Subscribe(/*Handle the result of each operation*/);
You should have tested your code as is because this is exactly what Rx imposes already.
Try this as a test:
void Main()
{
var timer = Observable.Interval(TimeSpan.FromMilliseconds(100));
using (timer.Do(x => Console.WriteLine("!")).Subscribe(tick => DoSomething()))
{
Console.ReadLine();
}
}
private void DoSomething()
{
Console.Write("<");
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff"));
Thread.Sleep(1000);
Console.WriteLine(">");
}
When you run this you'll get this kind of output:
!
<16:54:57.111>
!
<16:54:58.112>
!
<16:54:59.113>
!
<16:55:00.113>
!
<16:55:01.114>
!
<16:55:02.115>
!
<16:55:03.116>
!
<16:55:04.117>
!
<16:55:05.118>
!
<16:55:06.119
It is already ensuring that there's no overlap.
Here is a factory function that does exactly what you are asking for.
public static IObservable<Unit> Periodic(TimeSpan timeSpan)
{
return Observable.Return(Unit.Default).Concat(Observable.Return(Unit.Default).Delay(timeSpan).Repeat());
}
Here is an example usage
Periodic(TimeSpan.FromSeconds(1))
.Subscribe(x =>
{
Console.WriteLine(DateTime.Now.ToString("mm:ss:fff"));
Thread.Sleep(500);
});
If you run this, each console print will be roughly 1.5 seconds apart.
Note, If you don't want the first tick to run immediately, you could instead use this factory, which won't send the first Unit until after the timespan.
public static IObservable<Unit> DelayedPeriodic(TimeSpan timeSpan)
{
return Observable.Return(Unit.Default).Delay(timeSpan).Repeat();
}