Background Service in Singleton Scope - c#

in my rested-web-service project, i need to check database in one hour intervals, this method should continue to work all the time, even if there is not user, and this method is single per hosted server.(some thing like cron in linux but i will host is iis)
i have created a hostedService using microsft docs,
with merely modifying the execute intervals (from 5 sec to 1 hours),
**but problem occure after i deploy webservice solution in iis,this hosted servicerun if swagger page is open when i close browser or not calling rest-service, there is no logs in database
another problem is if a open if i open swagger ui and call webservices from mobile-client there is multiple logs instead of only one per hour.**
i bellive there is two problem here, first TimedHostedService scope is not singleton and it is created per request(scoped) and second problem is application is not kept alive where there is not any session in swagger-ui (or there is no user for werb services).
i hava tried creating Async Method for this purpose that method call-it self with task.delay(onHour)
but this didnt work either.
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromHours(1));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}

Related

How to use the database from a BackgroundService in .NET Web API

I am building a microservice which is supposed to send periodical notifications through various means.
To process the notifications and triggers I intend to use a BackgroundService which will look into database, call appropriate service based on notification type and mark into the database the notification as being sent.
How can I access the database from the background service in a safe way, not having concurrency issues?
Is it enough to inject IServiceProvider and create a scope?
public class MyBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public MyBackgroundService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await DoWorkAsync(stoppingToken);
}
private async Task DoWorkAsync(CancellationToken stoppingToken)
{
using (IServiceScope scope = _serviceProvider.CreateScope())
{
IRepository<Notification> notificationRepository =
scope.ServiceProvider.GetRequiredService<IRepository<Notification>>();
IRepository<NotificationLog> notificationLogRepository =
scope.ServiceProvider.GetRequiredService<IRepository<NotificationLog>>();
IUnitOfWork unitOfWork =
scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
while(true)
{
if(stoppingToken.IsCancellationRequested)
{
return;
}
var list = await notificationRepository.GetAll();
.....................................
await notificationLogRepository.Add(...);
await unitOfWork.Commit();
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
await base.StopAsync(stoppingToken);
}
}
Is it enough to inject IServiceProvider and create a scope?
Yes.
While you can create a single scope for your entire background worker's lifetime, you also can create a scope per "invocation" - in this case, you could create a new scope each time the timer goes off. That way, if there's ever a situation where the next one starts before the current one completes, the two invocations will be guaranteed to have different scopes.
If your "timer" is just doing an await Task.Delay, though, then there's no possibility of overlapping invocations and separate scopes aren't necessary. Some people prefer them anyway, since "invocation" and "scope" conceptually go well together.

Fire and Forget database-related method in Hosted Service

I'm having issues finding a solution to my problem and mostly because i don't understand them completely so, here I am asking your support.
I need to fire and forget a method that selects and updates records from database, the problem is I have a 15seconds range between record creation and its appearance in my database (synchronization issue not fixable by me, so i have to accept it) without freezing user's interface and meanwhile letting it create other records.
I tried to simply Task.Run(method) it but every time it's fired the dbContext it's refreshed so nothing is done.
Googling around I found a lot of IHostedService and BackgroundService solutions but i really can't get to the point in them: if I understand what i'm trying to do, I need to call an Hosted Service and passing them a scoped version of my dbContext so every fired method will have it's own dbContext and they could work simultaneously. But can't really get HOW TO DO that.
I managed my code in various layers and repositories, so I'll try to be as clear as possible.
Controller:
public class RMAController : BaseController
{
private readonly ApplicationServiceRecords applicationServiceRecords;
public RMAController(ApplicationServiceRecords
applicationServiceRecords,
IConfiguration configuration,
AuthenticationService authenticationService,
AuthorizationService authorizationService)
: base(
configuration,
authenticationService,
authorizationService)
{
this.applicationService = applicationService;
this.applicationServiceRecords= applicationServiceRecords;
}
[HttpPost]
[Authorize]
public async Task<IActionResult>CreateRecord(
ResponseCreateRecord viewModel)
{
if (!ModelState.IsValid)
return Content("Error X");
//this is the method i want to fire and forget
await ApplicationServiceRecords.CreateRecordAsync(viewModel);
return RedirectToAction("TestPage");
}
}
Inside "CreateRecordAsync" i call other method's from Domain Layer that create, waits and update the record (again, can't get rid of waits nor create it without the need to update it immediately)
I tried using BackgroundService, this way:
public class BackgroundWorkerQueue
{
private ConcurrentQueue <Func< CancellationToken,Task >> _workItems = new ConcurrentQueue < Func < CancellationToken,Task >> ();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public async Task < Func < CancellationToken,
Task >> DequeueAsync(CancellationToken cancellationToken) {
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out
var workItem);
return workItem;
}
public void QueueBackgroundWorkItem(Func < CancellationToken, Task > workItem) {
if (workItem == null) {
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
}
public class LongRunningService: BackgroundService {
private readonly BackgroundWorkerQueue queue;
private readonly ILogger < LongRunningService > _logger;
private readonly MyContext _dbcontext;
public LongRunningService(BackgroundWorkerQueue queue, ILogger < LongRunningService > logger, IServiceProvider serviceProvider) {
_logger = logger;
_dbcontext = serviceProvider.CreateScope().ServiceProvider.GetRequiredService <MyContext > ();//thought thiw could be the solution, yet nope (probably can't get how to use it)
this.queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await queue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}
Added them in startup:
services.AddHostedService<LongRunningService>();
services.AddSingleton<BackgroundWorkerQueue>();
And Fired the method using(?) this from controller :
_backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>{
ApplicationServiceRecords.CreateRecordAsync(viewModel); });
But I got "Invalid attempt to call ReadAsync when reader is closed." on first attemp using DB in a simple select client = await repository.GetClienteByIdAsync(client.Id);
And that's all.
I'm sorry for bad english/ bad programming/bad explanation, and thank you in advance to everyone'll help.

IHostedService Error in ASP.NET Core app - why is the object already disposed?

I have created a Background Service in my Server Solution
public class PurgeService : IHostedService, IDisposable
{
private readonly IServiceProvider _provider;
private Timer timer;
public PurgeService(IServiceProvider serviceProvider)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
_provider = scope.ServiceProvider;
}
}
public void Dispose()
{
timer?.Dispose();
}
public Task Purge(IServiceProvider serviceProvider)
{
var dbcontext = serviceProvider.GetRequiredService<ApplicationDBContext>();
var setting = dbcontext.AppSet.First();
double deletetime = setting.PurgeTimer *(1);
DateTime deletedate = DateTime.Now.AddHours(deletetime);
string deleteSQL = $"DELETE FROM Notifications WHERE CreatedDate > {deletedate}"
}
public Task StartAsync(CancellationToken cancellationToken)
{
timer = new Timer(x => Purge(_provider), null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
And added it to the Startup.cs
services.AddHostedService<PurgeService>();
My goal is to have a background service that checks every 10 seconds if there are notifications older than allowed and deletes them.
But when I run the app an error occurs
"System.ObjectDisposedException: "Cannot access a disposed object."
How can I implement this correctly?
Your constructor seems to be establishing a scope, and immediately disposing the scope. Your provider is tied to that scope so gets nuked immediately in the constructor when Dispose() is called on the scope due to using.
Background services are usually singletons with a lifetime equivalent to the lifetime of the the application. It seems to me you should register your service as a singleton outside of the service class itself. Something like
public class PurgeService : IHostedService
{
// no weird constructor taking in IServiceProvider
}
Also, using an IoC inside of a class is a bit of an antipattern. Inject a DB context factory instead of trying to resolve through a container inside your class.
The problem was that the database table was empty, but the null exception threw a completely different exception.

How is token.IsCancellationRequested false when I hit CTRL C?

The TLDR: Hitting CTRL C works for the initial worker service code, but adding in any sort of complexity seems to break the functionality.
If I create a blank C# worker service project, I get the following code:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
Playing around with this, if I run from the command line, and then hit CTRL C, I see the message:
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
And all is well. The app exits. As I began working on this project, I added in a TCP client, and I was always careful to keep this basic framework intact.
However, in testing it seems like I could sometimes hit CTRL C, see the application shutting down message, but the app does not exit. At first it seemed like reading IsCancellationRequested was causing the event to reset, so I changed to a class level variable that periodically reads that bool and used the bool everywhere else for the check. I thought that fixed the problem, but it was probably just a lucky coincidence. Now, I can firmly say the app does not exit when I hit CTRL C. If I add in a breakpoint, I can see when the code reaches the check that IsCancellationRequested returns false, even though I hit CTRL C and saw the app exiting message before the breakpoint was hit.
Does this thing even work? If no one has any answers, I will see if I can create a minimal example that exhibits the behavior. I tested the fresh worker project code I posted and it definitely works as expected. I don't see how my additions could cause any problems.
EDIT:
Here is an extremely minimal addition that will reproduce this bug (?). Connecting to 127.0.0.1 will of course fail, but the cancellation info is gone by the time that exception is handled:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
try
{
using (var client = new TcpClient("127.0.0.1", 8090))
using (var stream = client.GetStream())
{
_logger.LogInformation("connected to socket");
}
}
catch (Exception ex)
{
_logger.LogError("Exception in ExecuteAsync {mess}, {trace}", ex.Message, ex.StackTrace);
}
}
}
}
I can't get the log to stay in a code block in my post here, but it just shows application shutting down, and the loop never exits.
EDIT 2:
How is this not a bug? Calling synchronous code causing the rest of the program to behave improperly? Per Stephen's suggestion, I made the following change and now it properly exits:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
try
{
using (var client = new TcpClient())
{
await client.ConnectAsync("127.0.0.1", 8090);
using (var stream = client.GetStream())
{
_logger.LogInformation("connected to socket");
}
}
}
catch (Exception ex)
{
_logger.LogError("Exception in ExecuteAsync {mess}, {trace}", ex.Message, ex.StackTrace);
}
}
}
}
EDIT 3:
During development and testing, I tested against Netcat running on a Linux system. When I did the final deploy today, the actual socket being connected to is a SICK controller. The async code did not work. I reverted it to the version I started with and it now works. It will run as a service that never stops, so I guess I don't care that it doesn't exit properly. But I am having some doubts about C# and async that I didn't have before. Yay.

HostedService: The instance of entity type cannot be tracked

I'm developing a web api with asp.net core 2.2 and ef core 2.2.1. The api, besides handling the restful requests made by an angular app, is in charge of processing some xml files that are used as an interface with other software. Files are local to the application server and detected through a FileWatcher.
I've noticed during my tests that when I reprocess multiple times an xml test file, starting from the second time the file is being reprocessed, I obtain the Exception:
System.InvalidOperationException: The instance of entity type
'QualityLot' cannot be tracked because another instance with the key
value '{QualityLotID: ...}' is already being tracked. When
attaching existing entities, ensure that only one entity instance with
a given key value is attached.
when I call the method DbContext.QualityLot.Update(qualityLot);
The "processing file" service and the service it's using are configured into the Startup.cs file as following:
services.AddHostedService<InterfaceDownloadService>();
services.AddTransient<IQLDwnldService, QLDwnldService>();
the db context is configued like this:
services.AddDbContext<MyDbContext>(cfg =>
{
cfg.UseSqlServer(_config.GetConnectionString("LIMSConnectionString"));
});
and the class looks like:
public class InterfaceDownloadService : BackgroundServiceBase
{
[...]
public InterfaceDownloadService(IHostingEnvironment env,
ILogger<InterfaceDownloadService> logger,
IServiceProvider serviceProvider)
{
_ServiceProvider = serviceProvider;
}
[...]
private void processFiles()
{
[...]
_ServiceProvider.GetService<IQLDwnldService>().QLDownloadAsync(ev);
}
}
public abstract class BackgroundServiceBase : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
Here the critical point, where I have the exception:
public async Task QLDownloadAsync(FileReceivedEvent fileReceivedEvent)
{
Logger.LogInformation($"QLDwnld file {fileReceivedEvent.Event.FullPath} received for Processing");
try
{
QualityLotDownload qualityRoutingDwnld = deserializeObject<QualityLotDownload>(fileReceivedEvent.XsltPath, fileReceivedEvent.Event.FullPath);
Logger.LogDebug($"QLDwnld file {fileReceivedEvent.Event.FullPath} deserialized correctly. Need to determinate whether Insert or Update QualityLot {qualityRoutingDwnld.QualityLots.QualityLot.QualityLotID}");
for (int remainingRetries = fileReceivedEvent.MaxRetries; remainingRetries > 0; remainingRetries--)
{
using (var transaction = await DbContext.Database.BeginTransactionAsync())
{
try
{
var qualityLotDeserialized = qualityRoutingDwnld.QualityLots.QualityLot;
// insert the object into the database
var qualityLot = await DbContext.QualityLot.Where(x => x.QualityLotID == qualityLotDeserialized.QualityLotID).FirstOrDefaultAsync();
if (qualityLot == null) // INSERT QL
{
await InsertQualityLot(qualityLotDeserialized);
}
else // UPDATE QL
{
await UpdateQualityLot(qualityLot, qualityLotDeserialized);
}
[...]
transaction.Commit();
}
catch (Exception ex)
{
Logger.LogError(ex, $"Retry {fileReceivedEvent.MaxRetries - remainingRetries +1}: Exception processing QLDwnld file {fileReceivedEvent.Event.FullPath}.");
transaction.Rollback();
if (remainingRetries == 1)
{
return;
}
}
The method UpdateQualityLot(qualityLot, qualityLotDeserialized); is invoked because the entity already exists in the db
private async Task UpdateQualityLot(QualityLot qualityLot, QualityLotDownloadQualityLotsQualityLot qualityLotDeserialized)
{
[fields update]
DbContext.QualityLot.Update(qualityLot);
await DbContext.SaveChangesAsync();
}
The call to DbContext.QualityLot.Update(qualityLot); fails.
From what I can see the instance of QLDwnldService is new for every file being processed, in other words the following method returns every time a new object (as configured into Startup.cs)
_ServiceProvider.GetService<IQLDwnldService>().QLDownloadAsync(ev);
, while the DbContext is reused and that's probably the reason why the entity results already tracked.
I also tride to setup the non-tracking option in the DbContext OnConfiguring()
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
So my question is. What's wrong here? Maybe an architecture problematic or maybe a misleading configuration of e core? Thanks in advance for any support.
To be honest I could not figure out where your DBContext is actually injected from your code.
But from the error message I'd say your context is reused in a place where it should not be. So it's injected once and then used over and over and over.
You have registered your service as "Scoped" (because that's the default).
You should register it as "Transient" to ensure you will get a new instance on every call to your service provider:
services.AddDbContext<MyDbContext>(cfg =>
{
cfg.UseSqlServer(_config.GetConnectionString("LIMSConnectionString"));
},
ServiceLifetime.Transient);
Brad mentioned that this will have consequences for the rest of your application and he's right.
The better option might be to leave your DbContext scoped and inject the IServiceScopeFactory into your hosted service. Then create a new scope where you need it:
using(var scope = injectedServiceScopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<DbContext>();
// do your processing with context
} // this will end the scope, the scoped dbcontext will be disposed here
Please note that this still does not mean that you should access the DbContext in parallel. I don't know why your calls are all async. If you are actually doing parallel work, make sure you create one DbContext per thread.

Categories