I'm attempting to run an asynchronous task when loading a page in ASP.Net Core, i.e., I want the task to run as soon as the user routes to the page but for the page to be displayed before the task has completed. It seems that with ASP.Net core you use middleware to perform such tasks. So I attempted to add the following to Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
{
// Other configurations here
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.Contains("PageWithAsyncTask"))
{
var serviceWithAsyncTask = serviceProvider.GetService<IMyService>();
await serviceWithAsyncTask .DoAsync();
}
await next.Invoke();
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
The problem with the above is that there is a delay in the page loading until DoAsync has complete since we don't call next.Invoke() until DoAsync is complete. How do I correctly implement the above such that next.Invoke() is called immediately after I've got DoAsync running?
In ASP.NET Core 2 the IHostedService is designed to run your background tasks.
Register the IHostedService as Singleton and it is automatically started at startup:
implementing-background-tasks-in-microservices-with-ihostedservice-and-the-backgroundservice-class-net-core-2-x
asp-net-core-background-processing
Since Asp.Net core 2.1 to use background tasks it is very convenient to implement IHostedService by deriving from the BackgroundService base class. Here is the sample taken from here:
public class MyServiceA : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Console.WriteLine("MyServiceA is starting.");
stoppingToken.Register(() => Console.WriteLine("MyServiceA is stopping."));
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("MyServiceA is doing background work.");
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
Console.WriteLine("MyServiceA background task is stopping.");
}
}
Then just register it in Startup.ConfigureServices:
services.AddSingleton<IHostedService, MyServiceA>();
And as Stephen Cleary noted Asp.Net application may not be the best place for background tasks (e.g. when app is hosted in IIS it can be shut down because of app pool recycles), but for some scenarios it can be applied very well.
ASP.NET was not designed for background tasks. I strongly recommend using a proper architecture, such as Azure Functions / WebJobs / Worker Roles / Win32 services / etc, with a reliable queue (Azure queues / MSMQ / etc) for the ASP.NET app to talk to its service.
However, if you really want to - and are willing to accept the risks (specifically, that your work may be aborted), then you can use IApplicationLifetime.
Instead of
await serviceWithAsyncTask .DoAsync();
you could use
ThreadPool.QueueUserWorkItem(delegate {
SomeMethod();
});
In this approach an additional thread will be used from the thread pool which is of course a requirement if you want the code to run on a thread other than the main thread :-)
Any code placed after this block will run immediately. Also note that if your web server process (kestral) is recycled by IIS or whatever reverse proxy you are using then your background worker will be aborted immediately. So your background worker needs to be written defensively with this in mind.
Also please note that SomeMethod() is not itself an async method. But it's being called from a background thread so it's running asyncronously (i.e. independent of the main thread.)
Have a look at HangFire for managing background processing, works great in .Net Core: https://www.hangfire.io/
Related
I have a background service in my net 7 web api application:
public class MyBackgroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _provider.CreateScope();
// do some work
await Task.Delay(60000, stoppingToken);
}
}
}
Which I register like this:
services.AddHostedService<MyBackgroundService>();
How can I make it blocking so that the rest of my application doesn't start before the first do some work is executed?
It depends on how your application is wired. If you are using the default setup (for ASP.NET Core 7th version) then background services should be started up before the rest of the app and you can just override the BackgroundService.StartAsync (or just implement IHostedService directly) and perform first "do some work code" (potentially moving it to separate method) invocation in blocking manner (i.e. use blocking calls via Task.Result or Task.Wait(...) for async methods). Also do not forget to register this "blocking" hosted service first if you have multiple ones.
If you do not want to rely on the default order or you are using setup which has different order of startup (for example see this) then I would recommend to move the "do some work code" in some service, resolve it and invoke it before the app run:
var app = builder.Build();
// ...
var service = app.Services.GetRequiredService<IMyService>(); // create scope if needed
await service.DoSomeWorkCode(); // first run
app.Run();
P.S.
Also you can consider adding readiness probe to your app and middleware which will intercept all other requests if app is not ready (i.e. the initial run of "do some work code" has not finished).
Move the
// do some work code
into a separate method so you can call it at startup.
I have created a cron job in an API controller in .NET Core 6 as follows:
[HttpGet]
[Route("api/RunMyJob")]
public void RunMyJob()
{
RecurringJob.AddOrUpdate(() => jobmethod(), Cron.DayInterval(5));
}
[NonAction]
public async Task jobmethod()
{
// ........
}
I want to fire this job automatically when the project is run, without calling the API in the browser.
Please check the official doc about Background tasks with hosted services in ASP.NET Core.
Best code examples:
In ASP.NET Core 3.1, how can I schedule a background task (Cron Jobs) with hosted services for a specific date and time in the future?
I have a legacy application where HostingEnivronment.RegisterObject is used.
I have been tasked with converting this to asp.net core 2.0. however I am unable to find a way to either implement this or find an alternative to this in asp.net core 2.0.
the namespace Microsoft.AspNetCore.Hosting.Internal does not contain the registerobject method nor does it have the IRegisteredObject interface. I am at a loss on how to get this implemented.
The way to achieve similar goal in asp.net core is to use IApplicationLifetime interface. It has two properties two CancellationTokens,
ApplicationStopping:
The host is performing a graceful shutdown. Requests may still be
processing. Shutdown blocks until this event completes.
And ApplicationStopped:
The host is completing a graceful shutdown. All requests should be
completely processed. Shutdown blocks until this event completes.
This interface is by default registered in container, so you can just inject it wherever you need. Where you previously called RegisterObject, you instead call
// or ApplicationStopped
var token = lifeTime.ApplicationStopping.Register(OnApplicationStopping);
private void OnApplicationStopping() {
// will be executed on host shutting down
}
And your OnApplicationStopping callback will be invoked by runtime on host shutdown. Where you previously would call UnregisterObject, you just dispose token returned from CancellationToken.Register:
token.Dispose();
You can also pass these cancellation tokens to operations that expect cancellation tokens and which should not be accidentally interrupted by shutdown.
I am new to custom OWIN development let alone writing it in .NET Core. I started writing an OWIN module and I am able to hook it up in the Configure method in the Startup class. It works but it only executes the first time the application starts. I'd like this OWIN middleware to be called on each HTTP request.
Is this possible in .NET Core? It seems in .NET Framework 4.5 we can use "StageMarkers" (app.UseStageMarkers). It doesn't seem like this is an option in .NET Core.
Here is my implementation in startup:
app.UseMiddleware<SiteThemerMiddleware>();
This is my SiteThemerMiddleware (nothing really done yet to it):
public class SiteThemerMiddleware
{
private readonly RequestDelegate _next;
public SiteThemerMiddleware(RequestDelegate next)
{
_next = next;
//_logger = loggerFactory.CreateLogger<SiteThemerMiddleware>();
}
public async Task Invoke(HttpContext context)
{
//_logger.LogInformation("Handling request: " + context.Request.Path);
await _next.Invoke(context);
//_logger.LogInformation("Finished handling request.");
}
}
ASP.NET Core's pipeline is similar to but not directly OWIN (see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/owin for more details). Previously, katana was ASP.NET's implementation of OWIN on top of various hosts including System.Web.
Is this possible in .NET Core? It seems in .NET Framework 4.5 we can use "StageMarkers" (app.UseStageMarkers). It doesn't seem like this is an option in .NET CORE
UseStageMarkers has nothing to do with running middleware on each request. It was about interleaving middleware throughout various stages within the IIS Integrated pipeline on System.Web.
If you want to run logic on each request, then just write code in the Invoke method in your middleware. That is invoked per request.
All,
I am migrating existing Worker Role code to an Azure Web job. I am trying to use the WebJob SDK (1.0) so that I have the full integration with the Azure Web Site.
My difficultly is that the JobHost doesn't play nicely with jobs that are outside it's usual Attribute based invoke options (Queues, Blobs etc.)
I already have standard code that I cannot change to listen to Azure Queues/Topics etc. so I cannot use the WebJob code to do this.
Therefore I need to use the WebJob Call method:
var cancelTokenSource = new CancellationTokenSource();
var onStartMethod = typeof(Program).GetMethod("OnStart", BindingFlags.Static | BindingFlags.Public);
host.CallAsync(onStartMethod, cancelTokenSource.Token)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
NB: I am using my own CallAsync as all the advice is to use ConfigureAwait(false) when using libraries but the innards of JobHost doesn't do this, so I'm replicating it's "call" code myself but with the ConfigureAwait. The cancellation token doesn't do anything in JobHost as far as I can tell.
My problem is that I then need to call host.RunAndBlock(); to stop the job exiting, which is fine, but then I need to run some clean up processing. I can't make a new call to "CallAsync" with an OnStop method, as the Host is already being cancelled, so all I can do is make a direct call to my OnStop method. Unfortunately then I lose the ability to write to the WebSite log through the provided TextWriter class.
I think what I need is a way for JobHost to invoke my method within RunAndBlock, so I can then pick up the cancellation token fired when the host is shutting down, and then perform my cleanup code.... but there doesn't seem any way to do this.
Is there an obvious way I am missing? JobHost seems really poor at handling scenarios outside it's norm :(
As victor said, you could use Microsoft.Azure.WebJobs.WebJobsShutdownWatcher
This is an implementation of Amit solution : WebJobs Graceful Shutdown
So I've found a solution doing this :
No modification in the Program.cs
class Program
{
static void Main()
{
var host = new JobHost();
host.Call(typeof(Startup).GetMethod("Start"));
host.RunAndBlock();
}
}
the graceful shutdown goes in the Startup.cs :
public class Startup
{
[NoAutomaticTrigger]
public static void Start(TextWriter log)
{
var token = new Microsoft.Azure.WebJobs.WebJobsShutdownWatcher().Token;
//Shut down gracefully
while (!token.IsCancellationRequested)
{
// Do somethings
}
}
}
After the while loop, you could also stop started tasks.
NB: I am using my own CallAsync as all the advice is to use ConfigureAwait(false) when using libraries but the innards of JobHost doesn't do this, so I'm replicating it's "call" code myself but with the ConfigureAwait. The cancellation token doesn't do anything in JobHost as far as I can tell.
That's expected because you are passing your own cancellation token that is not cancelled by anyone. The webjobs cancellation token is cancelled when the shutdown notification is sent or the host is stopped/disposed. If you want to get a reference to the cancellation token from outside a webjob function, you have to keep it in a static variable or something similar.
If you want to use your own cancellation token you can use Microsoft.Azure.WebJobs.WebJobsShutdownWatcher to get the shutdown notification.
My problem is that I then need to call host.RunAndBlock(); to stop the job exiting, which is fine, but then I need to run some clean up processing. I can't make a new call to "CallAsync" with an OnStop method, as the Host is already being cancelled, so all I can do is make a direct call to my OnStop method. Unfortunately then I lose the ability to write to the WebSite log through the provided TextWriter class.
There is no out-of-the-box solution for this but you could call the cleanup function from the running webjob (if any). If you need cleanup outside of the function and you also want logging then you can only Console logging - write to console. The logs will be displayed on the WebJobs dashboard, on the webJob page. I'm curios, if you need cleanup outside of a function, what is the scenario?