.NET BackgroundService only runs once - c#

I have multiple BackgroundService's in my .NET Core app, but they only run once. Ideally I would have them run at the same time every day.
Code:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var timer = new CronTimer("0 23 * * *", TimeZoneInfo.Local);
do
{
// do something
}
while (await timer.WaitForNextTickAsync(stoppingToken));
}
For example, this will run at 11pm when the app is started, but everyday after that, it fails to execute

Related

Where to put method call so it execute at every 5 minutes

I have below code :
public async Task StartAsync(CancellationToken cancellationToken)
{
var cronExpressionVal = new Timer(async e => await GetCronExpression(cancellationToken), null, TimeSpan.Zero, new TimeSpan(0, 5, 0));
}
What I am trying to achieve is, GetCronExpression method should run at every 5 minutes.
But my problem is, when we first time run programme so it is coming in StartAsync method.
And it execute successfully.
Now it is not coming again in this method so my GetCronExpression method is not calling at every 5 minutes.
So my question is where should I put this GetCronExpression method call so it execute at every 5 minutes.
What I am trying to achieve is, GetCronExpression method should run at
every 5 minutes.
Well, couple of ways to handle this. However, the most efficient and easiest way to meet your requirement is to use PeriodicTimer. Importantly, you don't need to think where should you keep this. You can call your method every 5 minutes time interval from everywhere. It could be within middleware or any custom class , repository, even from controller.
Implementation Using Middleware:
public class AutoTimerMiddleware
{
private readonly RequestDelegate _next;
public AutoTimerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
int counter = 0;
while (await timer.WaitForNextTickAsync())
{
counter++;
// if (counter > 5) break;
CallThisMethodEvery5Second(counter);
}
// Move forward into the pipeline
await _next(httpContext);
}
public void CallThisMethodEvery5Second(int counter)
{
Console.WriteLine("Current counter: {0} Last Fired At: {1}", counter, DateTime.Now);
}
}
Note: When use in middleware, please register in program.cs file as following
app.UseMiddleware<AutoTimerMiddleware>();
Output:
Implementation Using Any Custom Class/Anywhere:
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
int counter = 0;
while (await timer.WaitForNextTickAsync())
{
counter++;
if (counter > 5) break;
CallThisMethodEvery5Second(counter);
}
Note: You will call your method GetCronExpression within the WaitForNextTickAsync so that will be called at your given time frame. For the demo, I am calling this in every 5 seconds.
Method To Call:
public void CallThisMethodEvery5Second(int counter)
{
Console.WriteLine("Current counter: {0} Last Fired At: {1}",counter, DateTime.Now);
}
Output:
You have to keep reference to timer otherwise it will be garbage collected:
As long as you are using a Timer, you must keep a reference to it. As with any managed object, a Timer is subject to garbage collection when there are no references to it. The fact that a Timer is still active does not prevent it from being collected.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer?view=net-7.0#remarks
If it's not the case, I suggest including more context.

.NET5 Background Service Concurrency

I have a background service that will be started when the application performing startup. The background service will start to create multiple tasks based on how many workers are set. As I do various trials and monitor the open connection on DB. The open connection is always the same value as the worker I set. Let say I set 32 workers, then the connection will be always 32 open connections shown as I use query to check it. FYI I am using Postgres as the DB server. In order to check the open connection, I use the query below to check the connection when the application is running.
select * from pg_stat_activity where application_name = 'myapplication';
Below is the background service code.
public class MessagingService : BackgroundService {
private int worker = 32;
protected override async Task ExecuteAsync(CancellationToken cancellationToken) {
var tasks = new List<Task>();
for (int i=0; i<worker; i++) {
tasks.Add(DoJob(cancellationToken));
}
while (!cancellationToken.IsCancellationRequested) {
try {
var completed = await Task.WhenAny(tasks);
tasks.Remove(completed);
} catch (Exception) {
await Task.Delay(1000, cancellationToken);
}
if (!cancellationToken.IsCancellationRequested) {
tasks.Add(DoJob(cancellationToken));
}
}
}
private async Task DoJob(CancellationToken cancellationToken) {
using (var scope = _services.CreateScope()) {
var service = scope.ServiceProvider
.GetRequiredService<MessageService>();
try {
//do select and update query on db if null return false otherwise send mail
if (!await service.Run(cancellationToken)) {
await Task.Delay(1000, cancellationToken);
}
} catch (Exception) {
await Task.Delay(1000, cancellationToken);
}
}
}
}
The workflow is not right as it will keep creating the task and leave the connection open and idle. Also, the CPU and memory usage are high when running those tasks. How can I achieve like when there is no record found on DB only keep 1 worker running at the moment? If a record or more is found it will keep increasing until the preset max worker then decreasing the worker when the record is less than the max worker. If this question is too vague or opinion-based then please let me know and I will try my best to make it as specific as possible.
Update Purpose
The purpose of this service is to perform email delivery. There is another API that will be used to create a scheduled job. Once the job is added to the DB, this service will do the email delivery at the scheduled time. Eg, 5k schedule jobs are added to the DB and the scheduled time to perform the job is '2021-12-31 08:00:00' and the time when creating the scheduled job is 2021-12-31 00:00:00'. The service will keep on looping from 00:00:00 until 08:00:00 with 32 workers running at the same time then just start to do the email delivery. How can I improve it to more efficiency like normally when there is no job scheduled only 1 worker is running. When it checked there is 5k scheduled job it will fully utilise all the worker. After 5k job is completed, it will back to 1 workers.
My suggestion is to spare yourself from the burden of manually creating and maintaining worker tasks, by using an ActionBlock<T> from the TPL Dataflow library. This component is a combination of an input queue and an Action<T> delegate. You specify the delegate in its constructor, and you feed it with messages with its Post method. The component invokes the delegate for each message it receives, with the specified degree of parallelism. When there are no more messages to send, you notify it by invoking its Complete method, and then await its Completion so that you know that all work that was delegated to it has completed.
Below is a rough demonstration if how you could use this component:
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
var processor = new ActionBlock<Job>(async job =>
{
await ProcessJob(job);
await MarkJobAsCompleted(job);
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 32
});
try
{
while (true)
{
Task delayTask = Task.Delay(TimeSpan.FromSeconds(60), cancellationToken);
Job[] jobs = await FetchReadyToProcessJobs();
foreach (var job in jobs)
{
await MarkJobAsPending(job);
processor.Post(job);
}
await delayTask; // Will throw when the token is canceled
}
}
finally
{
processor.Complete();
await processor.Completion;
}
}
The FetchReadyToProcessJobs method is supposed to connect to the database, and fetch all the jobs whose time has come to be processed. In the above example this method is invoked every 60 seconds. The Task.Delay is created before invoking the method, and awaited after the returned jobs have been posted to the ActionBlock<T>. This way the interval between invocations will be stable and consistent.

Quartz doesnt end job and/or trigger chained job

I have two quartz jobs, one for Downloading (whose source only is available during specific times) and one for Compressing (to compress the downloaded data), which I have chained with JobChainingJobListener.
However, the first Download job never completes (I am spinning on the CancellationToken but it never gets set) - similar to this blog https://blexin.com/en/blog-en/stopping-asynchronous-jobs-of-quartz-3/
I am not sure if Quartz supports external runtime duration, or if I need to manage the runtime within the job (i.e. run for X number of hours and then return). To separate concerns, externally managing this to the job would be best.
In my demo below I have set to run 30 seconds from starting the app and set to end running 30 seconds after it started (but for my real version I run between 9am to 6pm, in the provided timezone).
Greatly appreciate advise as to what i am doing wrong.
var scheduler = await StdSchedulerFactory.GetDefaultScheduler();
var schedule = SchedulerBuilder.Create();
var now = DateTime.Now.TimeOfDay;
var startAt = now.Add(TimeSpan.FromSeconds(30));
var endAt = startAt.Add(TimeSpan.FromSeconds(30));
Console.WriteLine($"Run from {now} to {endAt}");
var trigger = TriggerBuilder
.Create()
.ForJob(downloadJob)
.WithDailyTimeIntervalSchedule(x => x.InTimeZone(timeZoneInfo)
.StartingDailyAt(new TimeOfDay(startAt.Hours, startAt.Minutes, startAt.Seconds))
.EndingDailyAt(new TimeOfDay(endAt.Hours, endAt.Minutes, endAt.Seconds)))
.Build();
var chain = new JobChainingJobListener("DownloadAndCompress");
chain.AddJobChainLink(downloadJob.Key, compressJob.Key);
scheduler.ListenerManager.AddJobListener(chain, GroupMatcher<JobKey>.AnyGroup());
await this.scheduler.ScheduleJob(downloadJob, trigger, cancellationToken);
await this.scheduler.AddJob(compressJob, true, true, cancellationToken);
await this.scheduler.Start(cancellationToken);
And my template jobs are below:
class DownloadJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
Console.WriteLine("Running download job");
var cancellationToken = context.CancellationToken;
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000, cancellationToken);
}
Console.WriteLine("End download job");
}
}
class CompressJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
Console.WriteLine("Running compress job");
var cancellationToken = context.CancellationToken;
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000, cancellationToken);
}
Console.WriteLine("End compress job");
}
}

Windows service doesn't wait for running tasks to get finished

I am writing a windows service which creates a couple of parallels tasks to run:
Following is the sample code snippet:
private static void TaskMethod1()
{
//I am doing a bunch of operations here, all of them can be replaced with a sleep for 25 minutes
}
private static async Task TaskMethod()
{
while(runningService)
{
// Thi will create more than one task in parallel to run and each task can take upto 30 minutes to finish
Task.Run(() => TaskMethod1(arg1);
}
}
internal static void Start()
{
runningService = true;
Task1 = Task.Run(() => TaskMethod());
}
internal static void Stop()
{
runningService = false;
Task1.Wait();
}
Now when I stop the service, it will not create any new tasks because runningService = false but windows service doesn't wait for 30 minutes for already running tasks to get finished.
Now I read that there is an x minutes timeout for service and it can be changed using registry settings, I was just wondering if there is the way such that the service will wait for each task to be finished instead of hardcoding that time via the registry.
In my .net core services i use the interface IHostApplicationLifetime to intercept when my service is beign closed, by registering an action to call using IHostApplicationLifetime.ApplicationStopped.Register(Action callback) .
Then in the callback you could wait for the task to complete.

Cancelling async method that calls events

I have a headless UWP application that uses an external library to connect to a serial device and send some commands. It runs an infinite loop (while true) with a 10 minute pause between loops. The measurement process takes around 4 minutes.
The external library needs to run 3 measurements and after each it signals by raising an event. When the event is raised the 4th time I know that I can return the results.
After 4 hours (+/- a few seconds) the library stops raising events (usually it raises the event one or 2 times and then it halts, no errors, nothing).
I implemented in DoMeasureAsync() below a CancellationTokenSource that was supposed to set the IsCancelled property on the TaskCompletionSource after 8 minutes so that the task returns and the loop continues.
Problem:
When the measurement does not complete (the NMeasureCompletionSource never gets its result set in class CMeasure), the task from nMeasureCompletionSource is never cancelled. The delegate defined in RespondToCancellationAsync() should run after the 8 minutes.
If the measurement runs ok, I can see in the logs that the code in the
taskAtHand.ContinueWith((x) =>
{
Logger.LogDebug("Disposing CancellationTokenSource...");
cancellationTokenSource.Dispose();
});
gets called.
Edit:
Is it possible that the GC comes in after the 4 hours and maybe deallocates some variables and doing so makes the app to not be able to send the commands to the sensor? - It is not the case
What am I missing here?
//this gets called in a while (true) loop
public Task<PMeasurement> DoMeasureAsync()
{
nMeasureCompletionSource = new TaskCompletionSource<PMeasurement>();
cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(8));
var t = cMeasure.Run(nitrateMeasureCompletionSource, cancellationTokenSource.Token);
var taskAtHand = nitrateMeasureCompletionSource.Task;
taskAtHand.ContinueWith((x) =>
{
Logger.LogDebug("Disposing CancellationTokenSource...");
cancellationTokenSource.Dispose();
});
return taskAtHand;
}
public class CMeasure
{
public async Task Run(TaskCompletionSource<PMeasurement> tcs, CancellationToken cancellationToken)
{
try
{
NMeasureCompletionSource = tcs;
CancellationToken = cancellationToken;
CancellationToken.Register(async () => await RespondToCancellationAsync(), useSynchronizationContext: false);
CloseDevice(); //Closing device if for some reason is still open
await Task.Delay(2500);
TheDevice = await GetDevice();
measurementsdone = 0;
Process(); //start the first measurement
}
catch (Exception ex)
{
DisconnectCommManagerAndCloseDevice();
NMeasureCompletionSource.SetException(ex);
}
}
public async Task RespondToCancellationAsync()
{
if (!NitrateMeasureCompletionSource.Task.IsCompleted)
{
Logger.LogDebug("Measure Completion Source is not completed. Cancelling...");
NMeasureCompletionSource.SetCanceled();
}
DisconnectCommManagerAndCloseDevice();
await Task.Delay(2500);
}
private void Process()
{
if (measurementsdone < 3)
{
var message = Comm.Measure(m); //start a new measurement on the device
}
else
{
...
NMeasureCompletionSource.SetResult(result);
}
}
//the method called when the event is raised by the external library
private void Comm_EndMeasurement(object sender, EventArgs e)
{
measurementsdone++;
Process();
}
}
After more testing I have reached the conclusion that there is no memory leak and that all the objects are disposed. The cancellation works well also.
So far it appears that my problem comes from the execution of the headless app on the Raspberry Pi. Although I am using the deferral = taskInstance.GetDeferral(); it seems that the execution is stopped at some point...
I will test more and come back with the results (possibly in a new post, but I will put a link here as well).
Edit:
Here is the new post: UWP - Headless app stops after 3 or 4 hours
Edit 2:
The problem was from a 3rd party library that I had to use and it had to be called differently from a headless app. Internally it was creating its own TaskScheduler if SynchronizationContext.Current was null.

Categories