dotnet console app, using generic host, HostedService, Windows Task Scheduler stays in running state - .net-core

Trying to figure out why my console app won't stop running.
Using the following approach in a dotnet core application main method:
await new HostBuilder().
...
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyHostedService>();
})
.UseConsoleLifetime()
.Build()
.RunAsync();
Publishing and scheduling that task from the Windows Task Scheduler using the following settings works:
All good so far. All code is properly executed. However, the task stays running, the process never ends. (not even after pressing refresh on the UI of the task scheduler)
Is this expected? If not, how do I get the process to terminate?
If expected, does it still make sense then, to use Generic Host / Hosted Service in a scheduled console app that just starts, runs, and stops?

Answer based on Microsoft.Extensions.Hosting 2.2.0
This behavior is expected, due to your usage of the Generic Host:
It keeps running until shut down or disposed, and you have no shutdown mechanism in place. I assume you expect the Generic Host to shut down after IHostedService.StartAsync(CancellationToken) of your MyHostedService ran to completion. This is not the case, because there might be other IHostedService implementations registered and executed in sequence, and/or a long running BackgroundService which returns control when its ExecuteAsync(CancellationToken) is not completing synchronously to allow other services to run in parallel.
To stop your application gracefully after your MyHostedService completes when running the host via RunAsync, you should constructor-inject the IApplicationLifetime into your MyHostedService and call StopApplication after your Task has completed.
internal class MyHostedService : IHostedService
{
private readonly IApplicationLifetime _appLifetime;
public MyHostedService(IApplicationLifetime appLifetime)
{
_appLifetime = appLifetime;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await Task.Delay(1000); //your scheduled work
_appLifetime.StopApplication();
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Also, the application may be stopped via either AppDomain.CurrentDomain.ProcessExit or Console.CancelKeyPress, both events are subscribed to by the ConsoleLifetime, which is pre-registered as the default lifetime implementation.
You can read more about lifetime management in the docs.
Microsoft.Extensions.Hosting 3.0.0 - currently in preview - marked IApplicationLifetime obsolete and recommends using IHostApplicationLifetime instead

Related

.NET Generic Host - Is it possible to stop and restart a host?

Consider this extremely simple .NET Core 3.1 (and .NET 5) application with no special config or hosted services:
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
internal class Program
{
public static async Task Main(string[] args)
{
var builder = Host.CreateDefaultBuilder(args);
builder.UseWindowsService();
var host = builder.Build();
var fireAndforget = Task.Run(async () => await host.RunAsync());
await Task.Delay(5000);
await host.StopAsync();
await Task.Delay(5000);
await host.RunAsync();
}
The first Run (sent as a background fire and forget task only for the purpose of this test) and Stop complete successfully. Upon calling Run a second time, I receive this exception:
System.AggregateException : 'Object name: 'EventLogInternal'.Cannot access a disposed object. Object name: 'EventLogInternal'.)'
If I do the same but using StartAsync instead of RunAsync (this time no need for a fireAndForget), I receive a System.OperationCanceledException upon called StartAsync the second time.
Am I right to deduce that .NET Generic Host aren't meant to be stopped and restarted?
Why do I need this?
My goal is to have a single application running as a Windows Service that would host two different .NET Generic Host. This is based on recommendation from here in order to have separate configuration and dependency injection rules and message queues.
One would stay active for all application lifetime (until the service is stopped in the Windows services) and would serve as a entry point to receive message events that would start/stop the other one which would be the main processing host with full services. This way the main services could be in "idle" state until they receive a message triggering their process, and another message could return them to idle state.
The host returned by CreateDefaultBuilder(...).Build() is meant to represent the whole application. From docs:
The main reason for including all of the app's interdependent resources in one object is lifetime management: control over app startup and graceful shutdown.
The default builder registers many services in singleton scope and when the host is stopped all of these services are disposed or switched to some "stopped" state. For example before calling StopAsync you can resolve IHostApplicationLifetime:
var appLifetime = host.Services.GetService<IHostApplicationLifetime>();
It has cancellation tokens representing application states. When you call StartAsync or RunAsync after stopping, all tokens still have IsCancellationRequested set to true. That's why the OperactionCancelledException is thrown in Host.StartAsync.
You can list other services during configuration:
For me it sounds like you just need some background jobs to process messages but I've never used NServiceBus so I don't know how it will work with something like Hangfire. You can also implement IHostedService and use it in the generic host builder.
I'm doing something like:
do
{
using IHost host = BuildHost();
await host.RunAsync();
} while (MainService.Restart);
with MainService constructor:
public MainService(IHostApplicationLifetime HostApplicationLifetime)
MainService.Restart is a static bool set by the MainService itself in response to some event which also calls HostApplicationLifetime.StopApplication().

Nullreference exception does not show up when async Task discarded

I have an async Task with a method signature defined like this:
public async Task<bool> HandleFooAsync()
When executing this task in an async way and discarding the results, exceptions happening in this task do not show up in our logs.
_ = _slackResponseService.HandleFooAsync();
When I await the execution of the task I see the error in our logs
var result = await _slackResponseService.HandleFooAsync();
Is this expected behaviour? Is there a way to achieve a solution in between: "do not wait for the result, but log errors nevertheless.." ? We invested hours debugging our logging setup, just to learn that our logging setup is correct, but discard means in dotnet that everything is discarded - even logs. Which is a quite a new perspective for us, coming from a Python background.
Our logging setup follows the default logging setup for dotnet core 3 https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1
Yes, it is an expected behavior. Call in that way can be considered like an anti-pattern. You can read about it C# Async Antipatterns
You need something which is called as "Fire and forget". One of its implementation can be find in repo AsyncAwaitBestPractices (nuget available too).
A Task in .net and netcore is meant to be awaited. If it is not awaited, the scope might be destroyed before the async method has finished.
If you want to run tasks in the background and not wait for a result you can use BackgroundService in .netcore or a third party such as Hangfire which supports fire and forget jobs out of the box
https://medium.com/#daniel.sagita/backgroundservice-for-a-long-running-work-3debe8f8d25b
https://www.hangfire.io/
One solution is to subscribe to the TaskScheduler.UnobservedTaskException event. It is not ideal because the event is raised when the faulted Task is garbage collected, which may happen long after the actual fault.
Another solution could be to use an extension method every time a task is fired and forgotten. Like this:
_slackResponseService.HandleFooAsync().FireAndForget();
Here is a basic implementation of the FireAndForget method:
public async static void FireAndForget(this Task task)
{
try
{
await task;
}
catch (Exception ex)
{
// log the exception here
}
}

Can a Service starts several Tasks?

I need to use a Service which starts a Task more than once (= the same Service must run several parallelised Task). I read the JavaFX documentation, and they seem to say that a Service can run only one Task at once.
So if I call twice start with my Service object, the first Task returned by its createTask method would be stopped, as if I used restart after the first start.
However, that's not clear. As I told you, the documentation seems to tell that.
Indeed :
A Service creates and manages a Task that performs the work on the background thread.
Note that I could think they also say that a Service can have several Task started at the same time. Indeed :
a Service can be constructed declaratively and restarted on demand.
My question is : if I use N start in a row, will N Tasks be created AND KEEP EACH RUNNING ?
"If I use N start in a row, will N Tasks be created AND KEEP EACH RUNNING ?
In short, no.
"If I call start twice with my Service object..."
From the Javadocs:
public void start()
Starts this Service. The Service must be in the READY state to succeed in this call.
So if you call start() a second time without previously calling reset(), you will just get an exception. You can only call reset() if the Service is not in a RUNNING or SCHEDULED state. You can call restart(), which will have the effect of first canceling any current task, and then restarting the service. (This is what is meant by the documentation that says the "service can be restarted on demand".)
The net result of this is that a service cannot have two currently running tasks at the same time, since there is no sequence of calls that can get to that situation without throwing an IllegalStateException.
If you want multiple tasks running at once, simply create them yourself and submit them to an executor (or run each in its own thread, but an executor is preferred):
private final Executor exec = Executors.newCachedThreadPool(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
// ...
private void launchTask() {
Task<MyDataType> task = new Task<MyDataType>(){
#Override
protected Something call() {
// do work...
return new MyDataType(...);
}
};
task.setOnSucceeded(e -> { /* update UI ... */ });
task.setOnFailed(e -> { /* handle error ... */ });
exec.execute(task);
}

Azure web job singleton function is locked

I am using Azure web job to run some logic continuously. The function is a singleton function. However, I am getting "Waiting for lock" message after I tried to run this function after a restart of the web app. Does it mean that another instance of the same function is keeping the lock? How can I resolve this?
The function:
namespace Ns
{
public class Functions
{
[Singleton]
[NoAutomaticTriggerAttribute]
public static async Task ProcessMethod()
{
while(true){
//process logic here
await Task.Delay(TimeSpan.FromSeconds(20));}
}
}
}
The main program:
namespace ns
{
class Program
{
static void Main()
{
var host = new JobHost();
host.RunAndBlock();
}
}
}
The message that I got:
According to the Singleton attribute description the lock is adquired during function execution by a Blob lease.
If another function instance is triggered while this function is
running it will wait for the lock, periodically polling for it.
If you have more than one instance of your App Service Plan, this means that there are more than one Webjob and thus the Dashboard might be showing the locked status of the other Webjobs while one is running.
You can view the blob lease locks that are created on your storage account.
Another option is to try Listener Singletons but I never tried it with Manual triggers.
I disabled the production function in Azure and set the listenerlockPeriod to 15 seconds as described above.
This lessened the locking behavior significantly.

how to avoid any timeout during a long running method execution

I am working on an asp.net mvc 5 web application , deployed inside IIS-8, and i have a method inside my application to perform a long running task which mainly scans our network for servers & VMs and update our database with the scan results. method execution might last between 30-40 minutes to complete on production environment. and i am using a schedule tool named Hangfire which will call this method 2 times a day.
here is the job definition inside the startup.cs file, which will call the method at 8:01 am & 8:01 pm:-
public void Configuration(IAppBuilder app)
{
var options = new SqlServerStorageOptions
{
PrepareSchemaIfNecessary = false
};
GlobalConfiguration.Configuration.UseSqlServerStorage("scanservice",options);
RecurringJob.AddOrUpdate(() => ss.Scan(), "01 8,20 ***");
}
and here is the method which is being called twice a day by the schedule tool:-
public void Scan()
{
Service ss = new Service();
ss.NetworkScan().Wait();
}
Finally the method which do the real scan is (i only provide a high level description of what the method will do):-
public async Task<ScanResult> NetworkScan()
{
// retrieve the server info from the DB
// loop over all servers & then execute some power shell commands to scan the network & retrieve the info for each server one by one...
// after the shell command completed for each server, i will update the related server info inside the DB
currently i did some tests on our test environment and every thing worked well ,, where the scan took around 25 seconds to scan 2 test servers.but now we are planning to move the application to production and we have around 120++ servers to scan. so i estimate the method execution to take around 30 -40 minutes to complete on the production environment. so my question is how i can make sure that this execution will never expire , and the ScanNetwork() method will complete till the end?
Instead of worrying about your task timing out, perhaps you could start a new task for each server. In this way each task will be very short lived, and any exceptions caused by scanning a single server will not effect all the others. Additionally, if your application is restarted in IIS any scans which were not yet completed will be resumed. With all scans happening in one sequential task this is not possible. You will likely also see the total time to complete a scan of your entire network plummet, as the majority of time would likely be spent waiting on remote servers.
public void Scan()
{
Service ss = new Service();
foreach (var server in ss.GetServers())
{
BackgroundJob.Enqueue<Service>(s => s.ServerScan(server));
}
}
Now your scheduled task will simply enqueue one new task for each server.

Resources