2

I need to build a service that will run theoretically forever.

While running it will need to check a local cache once every couple of seconds, and if it finds new items in that cache it should create and execute a new Task for each new item.

My question is, what is a good approach to implementing such a service? My main concern is how should I do the time-based starting of async tasks.

I came up with two ways to do this

  1. Use a System.Timers.Timer to push events and then run code when the event is fired

  2. Create a method that will call itself using Task.Delay

Here are some examples of what I mean by those two methods:

Timer

public void Run()
{
    System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(2).TotalMilliseconds);
    CancellationTokenSource ts = new CancellationTokenSource();

    timer.Elapsed += async (sender, argz) => await CodeExecutor(ts.Token);
    timer.Start();
}

public async Task CodeExecutor(CancellationToken token)
{
    //Some logic
}

Task.Delay

private async Task RepeatableMethod(CancellationToken cancellationToken)
{
    try
    {
        //some logic
    }
    catch (Exception e)
    {
    }
    finally
    {
        await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken)
            .ContinueWith(_ => RepeatableMethod(cancellationToken),cancellationToken);
    }
}

I am not sure which method would better fit my scenario, and if there isn't a different way that would be even better than those two. My main concern is that I want the logic that I am executing every 2 seconds to be asynchronous and possibly parallel.

What would be considered a better approach in the following scenario, and is there another (good) way I can approach my case?

Kobek
  • 1,149
  • 6
  • 18
  • 40

3 Answers3

2

As far as I can see, it looks similar to a producer/consumer pattern.

You may use something like BlockingCollection for such scenario, but it's not async, it means a dedicated thread waiting for items will be required in this case. There is an async implementation by Stephen Cleary, which could provide similar functionality, in his AsyncEx library. More information about it could be found in his blog.

Or probably TPL Dataflow will be suitable, check this example.

Uladzislaŭ
  • 1,680
  • 10
  • 13
1

A crude asynchronous timer in the form below is certainly possible:

async Task Timer(Timespan interval, Func<Task> someCallback, CancellationToken token)
{
     while (!token.IsCancellationRequested)
     {
        await Task.Delay(interval, token);
        await someAction(); // Or someAction?.Invoke();
     }
}

There are some things to watch for, however:

  • In practice, using Task.Delay is not nearly as accurate as a System.Threading.Timer, since execution of the continuation needs to be scheduled - Under high load scenarios I've found that accuracies can often be worse than 15ms.
  • The time taken to in the callback (someAction) needs to be considered. If this isn't trivial, then you may need to add a StopWatch around the callback and then subtract the time taken in the callback from the next Task.Delay if you want reasonably accurate callback frequency.
  • You need to worry about re-entrancy. If the callback takes longer than the delay, then your timer will be throttled. In this case, you may need to remove await on the callback (if it is asynchronous), and at worst, if there is significant synchronous latency in the callback, then you may also need to launch the callback on it's own thread (e.g. Task.Run(() => Callback())) to ensure that the timer is re-entrant, possibly the risk of an unstable load on the callback.
  • You might also need to consider adding a timeout to the callback, with a mechanism like this
  • And of course the callback should be wrapped in an exception handler, and then decide on how to proceed when things go wrong. Retries are likely unnecessary, as the call will be made again on the next interval.

To me, I would typically stay with a System.Threading.Timer based solution - it is more accurate and is re-entrant, and a large number of timers can be created concurrently anyway, so there seems little going for a Task.Delay based timer.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • `Task.Delay` uses the same `System.Threading.Timer` internally, so it should be as accurate as that timer. – Evk May 17 '18 at 08:42
  • Very valid points to take into considaration. The callback is a bit of buisness logic plus some calls to external services, so I don't expect it to last anywhere near the delay timeout, but I will see what I can do about it, since I dont want to end up throttling the timer – Kobek May 17 '18 at 09:07
  • @Evk - I've clarified - in practice, because the continuation Task after the delay needs to be scheduled at the mercy of the scheduler, that the next invocation of the callback isn't going to be nearly as precise as `System.Threading.Timer`. [Many](https://stackoverflow.com/questions/37967123/task-delay-vs-thread-sleep-resolution-accuracy) [Others](https://stackoverflow.com/questions/31126500/how-can-i-ensure-task-delay-is-more-accurate) have noted this behaviour. – StuartLC May 17 '18 at 10:48
0

Could using events solve your problems? You could emit an event whenever something is added to the cache, and execute the adequate operation in the event listener.

Shocky2
  • 504
  • 6
  • 24
  • Its a long story, and I would need to explain a lot of the underlying structure, so short answer - no, executing the action on item added is not suitable atm – Kobek May 17 '18 at 09:01