rewrite an IHostedService to stop after all tasks finished - .net-core

I have an application that normally should be a simple console application to be programmed as a scheduled task from time to time called by the windows task scheduler.
The program should launch some updates on two databases, one service per one database. Say ContosoDatabase should be updated by the ContosoService.
Finally it was written as an .NET Core app using, and maybe is not the best choice, the IHostedServices as base for the service, like this:
public class ContosoService : IHostedService {
private readonly ILogger<ContosoService> _log;
private readonly IContosoRepository _repository;
private Task executingTask;
public ContosoService(
ILogger<ContosoService> log,
IContosoRepository repository,
string mode) {
_log = log;
_repository = repository;
}
public Task StartAsync(CancellationToken cancellationToken) {
_log.LogInformation(">>> {serviceName} started <<<", nameof(ContosoService));
executingTask = ExcecuteAsync(cancellationToken);
// If the task is completed then return it,
// this should bubble cancellation and failure to the caller
if (executingTask.IsCompleted)
return executingTask;
// Otherwise it's running
// >> don't want it to run!
// >> it should end after all task finished!
return Task.CompletedTask;
}
private async Task<bool> ExcecuteAsync(CancellationToken cancellationToken) {
var myUsers = _repository.GetMyUsers();
if (myUsers == null || myUsers.Count() == 0) {
_log.LogWarning("{serviceName} has any entry to process, will stop", this.GetType().Name);
return false;
}
else {
// on mets à jour la liste des employés Agresso obtenue
await _repository.UpdateUsersAsync(myUsers);
}
_log.LogInformation(">>> {serviceName} finished its tasks <<<", nameof(ContosoService));
return true;
}
public Task StopAsync(CancellationToken cancellationToken) {
_log.LogInformation(">>> {serviceName} stopped <<<", nameof(ContosoService));
return Task.CompletedTask;
}
}
and I call it from main like this:
public static void Main(string[] args)
{
try {
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex) {
Log.Fatal(ex, ">>> the application could not start <<<");
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host
.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => {
var config = hostContext.Configuration;
if (args.Contains("Alonso")) {
services
.AddHostedService(provider =>
new AlonsoService(
provider.GetService<ILogger<AlonsoService>>(),
provider.GetService<IAlonsoRepository>()));
}
// if there also Cedig in the list, they can be run in parallel
if (args.Contains("Contoso")) {
services
.AddHostedService(provider =>
new ContosoService(
provider.GetService<ILogger<ContosoService>>(),
provider.GetService<IContosoRepository>()));
}
});
Now, the problem, is surely, that the application will not stop once all updates finished.
Is there a way to quickly rewrite the application in order to make it stop after the second service finishes its tasks?
I tried to put the Environment.Exit(0); at the end
public static void Main(string[] args) {
try {
CreateHostBuilder(filteredArgs.ToArray()).Build().Run();
}
catch (Exception ex) {
//Log....
}
Environment.Exit(0); // here
}
but it does not seem to help: the application is still running after all task are completed.

Following #Maxim's suggestion, I found this dirty but working workaround, by injecting the IHostApplicationLifetime and the lastService boolean:
public ConsosoService(
IHostApplicationLifetime hostApplicationLifetime,
// ...
bool lastService)
{ ... }
public async Task StartAsync(CancellationToken cancellationToken)
{
// do the job
if (_lastService)
_hostApplicationLifetime.StopApplication();
// stops the application and cancel/stops other services as well
}

HostedServices are background services. It's the other way around: they can react to application start and stop events, so that they can end gracefully. They are not meant to stop your main application when finished, they potentially live as long as the application does.
I'd say you will be better served with simple Tasks and awaiting all of them. Or send some events when your background jobs finishes its work and handle them in main.
Whatever trigger you may choose you can stop .net app by injecting IHostApplicationLifetime and calling StopApplication() method on it. In earlier versions it's just IApplicationLifetime.

Looking at IHost Interface documentation the method run() does not stop until the host is shutdown. seems that StopAsync() did not stop the service. so Environment.Exit(0); was never reached. maybe use CancellationToken to forcefully end the host, or inject Environment.Exit(0); in ContosoService class if possible even though not optimal.

Here is another approach without need for creating hosted service
using var host = CreateHostBuilder(args).Build();
await host.StartAsync();
using var scope = host.Services.CreateScope();
var worker = scope.ServiceProvider.GetService<Worker>();
await worker!.Run();
await host.StopAsync();
IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(services => ConfigureServices(services));
void ConfigureServices(IServiceCollection services)
{
//main class which does the work
services.AddScoped<Worker>();
//do some DB operations
services.AddScoped<DbCtxt>();
}
Complete code https://github.com/raghavan-mk/dotnet/tree/main/DIInConsole

Related

how to deploy a kafka consumer being in pause mode until i signal to start consume the messages

I am using spring-kafka 2.2.8 and trying to understand if there is an option to deploy a kafka consumer being in pause mode until i signal to start consume the messages. Please suggest.
I see in the below post, we can pause and start the consumer but I need the consumer to be in pause mode when it's deployed.
how to pause and resume #KafkaListener using spring-kafka
#KafkaListener(id = "foo", ..., autoStartup = "false")
Then start it using the KafkaListenerEndpointRegistry when you are ready
registry.getListenerContainer("foo").start();
There is not much point in starting it in paused mode, but you can do that...
#SpringBootApplication
public class So62329274Application {
public static void main(String[] args) {
SpringApplication.run(So62329274Application.class, args);
}
#KafkaListener(id = "so62329274", topics = "so62329274", autoStartup = "false")
public void listen(String in) {
System.out.println(in);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so62329274").partitions(1).replicas(1).build();
}
#Bean
public ApplicationRunner runner(KafkaListenerEndpointRegistry registry, KafkaTemplate<String, String> template) {
return args -> {
template.send("so62329274", "foo");
registry.getListenerContainer("so62329274").pause();
registry.getListenerContainer("so62329274").start();
System.in.read();
registry.getListenerContainer("so62329274").resume();
};
}
}
You will see a log message like this when the partitions are assigned:
Paused consumer resumed by Kafka due to rebalance; consumer paused again, so the initial poll() will never return any records

Hosted service not terminating after Environment.Exit

I've got a .NET core 3.1 app with a hosted service that runs as a console application on Windows.
In case of an error I'm trying to terminate the worker with Environment.Exit(1).
Now the problem is that, if Enviroment.Exit() is called before any await in ExecuteAsync, the application does not terminate. It logs Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks. and then hangs indefinitely.
When I await anything before the call to Enviroment.Exit() it also logs that, but it terminates as expected.
Here is the simplest code that I could come up with to reproduce the problem.
The NotTerminatingWorker hangs forever, the TerminatingWorker terminates. The only difference is a tiny Task.Delay:
public class Program {
public static async Task Main(string[] args) {
using var host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) {
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) => { services.AddHostedService<NotTerminatingWorker>(); });
}
}
public class NotTerminatingWorker : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
Environment.Exit(1);
}
}
public class TerminatingWorker : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
await Task.Delay(1);
Environment.Exit(1);
}
}
I would expect that both behave the same way, but that's obviously not the case.
Any explanation for this would be greatly appreciated!
UPDATE: The application should be able to run both as a console application and as a Windows service. The non-zero return code is required to get it restarted if it crashes.
And apparently Windows does not restart services that exited with code 0.
I believe the behavior you're seeing is a side-effect of how the .NET Core runtime does its startup: it calls ExecuteAsync for each background worker and then waits for it to complete. So a synchronous ExecuteAsync can cause problems. I've used Task.Run to work around this.
In case of an error I'm trying to terminate the worker with Environment.Exit(1).
I recommend not using Environment.Exit at all. Instead, do a controlled shutdown by injecting IHostApplicationLifetime and calling StopApplication. This will trigger the stoppingToken for each of your background services, and if they ignore it, they will be forcibly terminated after a timeout.
Handling the hostLifetime events in the Main method did for me the job. This is working for me on .NET6
public static int Main(string[] args)
{
ExitCode = 0;
ILogger? logger = null;
try
{
var builder = CreateHostBuilder(args)
.Build();
var hostLifetime = builder.Services.GetRequiredService<IHostApplicationLifetime>();
logger = builder.Services.GetService<ILogger<Program>>();
// register on hostLifetime events for handling stopping and finalize
using var hostLtAppStopping = hostLifetime.ApplicationStopping.Register(() =>
{
// service is about to stop... do some cleanup stuff here
});
using var hostLtAppStopped = hostLifetime.ApplicationStopped.Register(() =>
{
logger?.LogDebug("Service graceful shout down, exit with code {exitCode}!", ExitCode);
Environment.Exit(ExitCode); // ExitCode is set by the caller of hostApplicationLifetime.StopApplication
});
// start the service
logger?.LogDebug("builder.Run()");
builder.Run();
}
catch (Exception e)
{
logger?.LogError(e, "Unhandled Exception occurred => exit with exit code 1!");
ExitCode = 1;
return ExitCode;
}
return ExitCode;
}

Start Service Bus Client from BackgroundService

I have a ServiceBusClient class that creates a QueueClient which is used to listen for messages on a bus. I have looked at the following articles to set this up:
Background tasks (Microsoft)
Hosted services (Microsoft)
Async and Await
My ServiceBusClient class that handles the QueueClient looks like this:
public class ServiceBusClient : IServiceBusClient
{
public ServiceBusClient(IEventService eventService, ServiceBusClientOptions options)
{
...
queueClient = new QueueClient(options.ConnectionString, options.QueueName);
}
public void Run()
{
RegisterOnMessageHandler();
}
private void RegisterOnMessageHandler()
{
...
queueClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
}
private async Task ProcessMessagesAsync(Message message, CancellationToken token)
{
var eventMessage = EventMessage.FromMessage(message);
await eventService.Write(eventMessage);
if (!token.IsCancellationRequested)
{
await queueClient.CompleteAsync(message.SystemProperties.LockToken);
}
}
private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs)
{
// log errors
...
return Task.CompletedTask;
}
}
I was hoping to launch from an IHostedService or even by extending the BackgroundService. In the examples I find, work is constantly being executed in a while loop which does not fit my scenario since I am only trying to run a single command.
So I created a super simple implementation like this:
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
serviceBusClient.Run();
while (!cancellationToken.IsCancellationRequested)
{
// empty loop to keep running for lifetime of pod
}
}
If removing the async I obviously need to return something. I tried Task.CompletedTask but that required me to change the return type to Task<Task>.
If I have the async in place, I will need to await something, but I am not sure what.
This does not feel right. I would assume I would need to change something in the ServiceBusClient, but I am unsure what, since the ProcessMessagesAsync is async and does the heavy lifting in the background from my understanding.
All I want is for my web app to start listening for messages until it dies. How can I do that?
I gave up on using BackgroundService and implemented IHostedService instead.
public class MessageListenerService : IHostedService
{
private readonly IServiceBusClient client;
private readonly ITelemetryClient applicationInsights;
public MessageListenerService(IServiceProvider serviceProvider)
{
client = serviceProvider.GetService<IServiceBusClient>();
applicationInsights = serviceProvider.GetService<ITelemetryClient>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
applicationInsights.TrackTrace(new TraceTelemetry("MessageListenerService is starting"));
client.Run();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
applicationInsights.TrackTrace(new TraceTelemetry("MessageListenerService is stopping"));
return client.Stop();
}
}
If you find issues with this code please let me know in the comments and I'll update as appropriate.
In the end we created a console app for it anyway.

DbContext Depedency Injection Issue - System.ObjectDisposedException: Cannot access a disposed object

I have written my first .Net core program using the MVC framework on a Ubuntu machine. In the program I am trying to interact with SQLite database. The database CRUD operations work fine when processed through the controller class. However when I tried to operate on the database outside the controller I get following error
"Unhandled Exception: System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'MyDbContext'."
For operation outside the controller class, I created a class named MyDbWatch.cs (in the project root director)
public interface IMyDbWatch { }
public class MyDbWatch : IMyDbWatch
{
private readonly MyDbContext _dbContext;
private static Timer _timer;
private AutoResetEvent _autoEvent = null;
public MyDbWatch(MyDbContext context)
{
_dbContext = context;
_autoEvent = new AutoResetEvent(false);
_timer = new Timer(
callback: async s => await OnTimerEventAsync(s),
state: _autoEvent,
dueTime: 5000,
period: 10000);
}
public async Task OnTimerEventAsync(Object stateInfo)
{
Console.WriteLine("retreiving from db - 1");
var ienStates = from m in _dbContext.IenState select m;
Console.WriteLine("retreiving from db - 2");
var listdb = await ienStates.ToListAsync();
Console.WriteLine("retreiving from db - 3");
}
}
Here how I inject different dependencies in Startup.cs file
public class Startup
{
private MyDbWatch _myDbWatch;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<IpointWebMcmContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("IpointContext")));
services.AddScoped<IMyDbWatch, MyDbWatch>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
IMyDbWatch dbwatch)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
_myDbWatch = dbwatch;
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The the timer callback function OnTimerEventAsync in MyDbWatch.cs gets called first time and debug text 'retreiving from db - 1' get printed in the console. And after that I get the error
Unhandled Exception: System.ObjectDisposedException: Cannot access a disposed object....
Any help resolve this issue would be highly appreciated. I need this kind of watch on the database to push data to the client through the use of SignalR hub framework (not included in the code yet).
And this would be why statics should be avoided. Virtually every time you have a static like this, some developer is going to trip over it, because they aren't considering how things actually work.
The static keyword isn't magic. You've got a scoped service where you want to persist state (your timer), so you just slap a static on it and call it a day. However, this service uses other scoped services (your context), which now are out of sync with this static timer, i.e. the timer sticks around, but the context doesn't.
First, if you need to maintain state across an application lifetime, you should be using a singleton scope. That frees you from the terror of static. However, then, you'll need to utilize the server-locator pattern to get your context, because you cannot inject a scoped instance into a singleton.
public class MyDbWatch : IMyDbWatch
{
private readonly IServiceProvider _serviceProvider;
private readonly Timer _timer;
private AutoResetEvent _autoEvent = null;
public MyDbWatch(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_autoEvent = new AutoResetEvent(false);
_timer = new Timer(
callback: async s => await OnTimerEventAsync(s),
state: _autoEvent,
dueTime: 5000,
period: 10000);
}
public async Task OnTimerEventAsync(Object stateInfo)
{
using (var scope = _serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
Console.WriteLine("retreiving from db - 1");
var ienStates = from m in context.IenState select m;
Console.WriteLine("retreiving from db - 2");
var listdb = await ienStates.ToListAsync();
Console.WriteLine("retreiving from db - 3");
}
}
}
Then, in ConfigureServices:
services.AddSingleton<IMyDbWatch, MyDbWatch>();
Now, I have no idea what you're actually trying to accomplish with any of this, as your code doesn't make much sense, but the above is the only way you're going to safely be able to do it.

MvvmCross Async command lock

I have alot of button in my application. They are placed next to each other. All of the methods are IMvxAsyncCommand type. I figured out some missmatches after tests done by users. I have found duplicate operations - two diffrent buttons are called in almost same time.
What did I do is created my own SafeAsyncCommand class and inheret from MvxAsyncCommand. My goal is to create delay between executes - I want to prevent double click in given delay in below case 0.5s.
There is my work:
public static class SafeCommandSettings
{
public static bool CanExecute { get; private set; }
public static TimeSpan Delay => TimeSpan.FromMilliseconds(500);
static SafeCommandSettings()
{
CanExecute = true;
}
public static async void Pause()
{
if (!CanExecute) return;
CanExecute = false;
await Task.Delay(Delay);
CanExecute = true;
}
}
public class SafeAsyncCommand : MvxAsyncCommand
{
public SafeAsyncCommand(Func<Task> execute, Func<bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
public SafeAsyncCommand(Func<CancellationToken, Task> execute, Func<bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
protected override async Task ExecuteAsyncImpl(object parameter)
{
if (!SafeCommandSettings.CanExecute) return;
SafeCommandSettings.Pause();
await base.ExecuteAsyncImpl(parameter);
}
}
public class SafeAsyncCommand<T> : MvxAsyncCommand<T>
{
public SafeAsyncCommand(Func<T, Task> execute, Func<T, bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
public SafeAsyncCommand(Func<T, CancellationToken, Task> execute, Func<T, bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
protected override async Task ExecuteAsyncImpl(object parameter)
{
if (!SafeCommandSettings.CanExecute) return;
SafeCommandSettings.Pause();
await base.ExecuteAsyncImpl(parameter);
}
}
I thought this is working but I saw users were able to do it again. Do I miss some knowledge about async methods or static thread safe?
Thanks in advance
In order to do so you can take advantage of MvxNotifyTask that is a wrapper of the Task that watches for different task states and you'll run on your command and do something like this (notice that you don't need the command to be MvxAsyncCommand):
public MvxNotifyTask MyNotifyTaskWrapper { get; private set; }
public MvxCommand MyCommand { get; private set; }
private void InitializeCommands()
{
// this command is executed only if the task has not started (its wrapper is null) or the task is not in progress (its wrapper is not IsNotCompleted)
this.MyCommand = new MvxCommand(() => this.MyNotifyTaskWrapper = MvxNotifyTask.Create(() => this.MyLogicAsync()),
() => this.MyNotifyTaskWrapper == null || !this.MyNotifyTaskWrapper.IsNotCompleted);
}
private async Task MyLogicAsync()
{
// my async logic
}
So as soon the async process is started the command can't be executed again preventing duplicate operations, and you can start it again when that task completes.
If you have to disable multiple commands execution when running some task just add the same CanExecute condition on the different commands or mix conditions of different MvxNotifyTask
Also check that the MvxNotifyTask raises property-changed notifications that you can subscribe to or bind to in your view displaying a "loading" or something like that when performing the operation.
Note: if you are using Mvx < 5.5 you won't have MvxNotifyTask but you can use NotifyTaskCompletion done by Stephen Cleary that is almost the same as MvxNotifyTask and it is from where MvxNotifyTask was based on.
HIH
Instead of delaying things, consider using AsyncLock by Stephen Cleary or lookup Interlocked.CompareExchange.
As far as I can tell from here, you shouldn't use a static CanExecute in your case, as it locks all commands using your "safe" command at once.
And there is the possibility of race conditions, since you aren't changing the value of CanExecute locked.

Resources