0

I'm creating a background service that needs to run each x seconds. It has to be in .net Framework as the client does not want to upgrade to core or install anything on the machine other than this app. So I am restricted to using the Windows Service

My main issue is that I'm going in a while(true) loop that checks for the passed time (yes, I know I could use a timer) and I'm not sure if I should add a thread. Sleep in the loop or just leave the while(true). My main concern is not to overload the CPU/memory.

var nextIteration = DateTime.Now.Add(TimeSpan.FromSeconds(timer * (-1)));

while (true)
{
    if (nextIteration < DateTime.Now)
    {
        RunService();
        nextIteration = DateTime.Now.Add(TimeSpan.FromSeconds(timer));
    }
}
Andrei
  • 17
  • 7
  • service is always running. also please provide more details through code. its not clear – Vivek Nuna Jan 10 '23 at 08:45
  • Any code to post? – Ergis Jan 10 '23 at 08:46
  • 1
    `while (true) { ... Thread.Sleep(ms); ... }` is always, 100%, never ever good. – Enigmativity Jan 10 '23 at 08:47
  • updated, the timer's time isnt that important, what i want is for it to finish its job and then wait 90 seconds let's say, and i'm not sure if i should Thread.Sleep within the while loop in order to avoid looping for nothing – Andrei Jan 10 '23 at 08:51
  • Related: [Run async method regularly with specified interval](https://stackoverflow.com/questions/30462079/run-async-method-regularly-with-specified-interval). Running a synchronous method is similar. – Theodor Zoulias Jan 10 '23 at 10:50
  • If your service is not async (and cannot be made such for whatever reason) then you should leave `Thread.Sleep`, otherwise you are wasting CPU for no reason. – Evk Jan 10 '23 at 11:25
  • its not async, sadly the .net framework servicebase doesnt support async. I was wondering if its better to loop for the timer period or do thread.Sleep/delay for the remainder and then loop again. But from what you say im guessing going the Sleep route is the way – Andrei Jan 10 '23 at 11:36
  • Yes because otherwise you keep one CPU core busy 100% of the time for absolutely nothing (endlessly checking that if condition) – Evk Jan 10 '23 at 11:41
  • That said, ServiceBase has OnStart and OnStop, it does not define what happens between those events. So it seems not true that it "doesn't support async". You still can implement your check with async and `Task.Delay` (and cancellation token which you will cancel in OnStop) as current answer describes. – Evk Jan 10 '23 at 11:45
  • ok but that means i need to do some Task.Run and wait for the delay. isnt block the thread with task.Wait() risky? – Andrei Jan 10 '23 at 11:59
  • You don't need to do that, just define async method and execute it from OnStart. For example take code from current answer and in OnStart do `ExecuteAsync()`. Ensure that you catch all exceptions there though. – Evk Jan 10 '23 at 12:01
  • ah ok, that sounds like a good choice. Ill try that out – Andrei Jan 10 '23 at 12:02
  • 1
    One thing to note is that the part of async method before the first await always executes on the caller thread. So, if you do heavy work in `RunService` and you call that before first `await` (like in current answer for example, do work goes before first await) - you might want to add `await Task.Yield()` at the beginning of the `ExecuteAsync`. That's because `OnStart` should complete quickly. – Evk Jan 10 '23 at 12:11

1 Answers1

6

If you are implementing a service of type BackgroundService you should consider using the CancellationToken in your while:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {    
        try
        {
             //do work
             await Task.Delay(TimeSpan.FromSeconds(x), stoppingToken);
        }
        catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested)
        {
            //handle cancelation requested exception
        }
        catch (Exception ex)
        {
            //handle ex
        }          
    }
}

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio

Radu Hatos
  • 351
  • 1
  • 6
  • 2
    It also maybe useful to catch TaskCancelledException :) – Michał Turczyn Jan 10 '23 at 08:49
  • ok but this doesnt answer my question, while it might be a better option, i'll still have a while true loop and was wondering if i should have the task.Delay/sleep or just let it loop until the next iteration. – Andrei Jan 10 '23 at 09:30
  • @Andrei this `while` loop iterates only once per `RunService` execution, not billions of times in a tight loop. It's a vast improvement over your existing code. – Theodor Zoulias Jan 10 '23 at 10:55
  • ok but the problem is the ServiceBase for backgroundServices in .net framework doesnt support async, i would end up doing something of sorts for the delay: private void Delay(int timer) { var heartBeatTask = Task.Run(async () => await Task.Delay(TimeSpan.FromSeconds(timer))); heartBeatTask.Wait(); } – Andrei Jan 10 '23 at 11:21
  • isnt it risky to block the thread with wait? – Andrei Jan 10 '23 at 11:22
  • @Radu, maybe im wrong but isnt the background service a concept in .net core. as far as i looked, i only found the concept of Windows Service that has to implement ServiceBase in .net framework – Andrei Jan 10 '23 at 11:25