-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Minimal Workers #64874
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
|
Should there be two type of background service method? One for recurring which would be in a while loop and the other just run for once and let user decide how the service is run, it could be useful when listening to an events instead of timer. Also the methods could get a public static class BackgroundServiceExtensions
{
public static IServiceCollection AddBackgroundService(this IServiceCollection services, Delegate process);
public static IServiceCollection AddRecurringBackgroundService(this IServiceCollection services, Delegate process);
} And since this API in on |
Tagging subscribers to this area: @dotnet/area-extensions-hosting Issue DetailsBackground and MotivationI had this idea over the weekend when building a minimal API, that would be augmented with a Proposed APIThis is in no way proposed to be final, but, hopefully the idea will come across. Let's say I have 3 different processes I want to run on intervals in the background of an ASP.NET app, but none of them are really big enough to warrant a whole new class. It'd be great if I could just do something like this in my Usage Examplesbuilder.Services.AddBackgroundProcess(async (token) =>
{
Console.WriteLine($"First Process Running at {DateTime.Now}");
await Task.Delay(1000);
});
builder.Services.AddBackgroundProcess(async (token) =>
{
Console.WriteLine($"Second Process Running at {DateTime.Now}");
await Task.Delay(5000);
});
builder.Services.AddBackgroundProcess(async (token) =>
{
Console.WriteLine($"Third Process Running at {DateTime.Now}");
await Task.Delay(10000);
});
var app = builder.Build(); Very Brief Implementation and UsageI wired up a super-simple implementation to support this usage scenario. This is in no way intended to be an actual proposal - surely it'd need some improvement and more consideration. namespace Microsoft.Extensions.Hosting
{
public class BackgroundProcessWrapper : BackgroundService
{
public BackgroundProcessWrapper(Func<CancellationToken, Task>? unitOfWork)
{
UnitOfWork = unitOfWork;
}
public Func<CancellationToken, Task>? UnitOfWork { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (UnitOfWork == null) return;
while (!stoppingToken.IsCancellationRequested)
{
await UnitOfWork(stoppingToken);
}
}
}
public static class BackgroundServiceExtensions
{
public static IServiceCollection AddBackgroundProcess(this IServiceCollection services, Func<CancellationToken, Task>? process)
{
services.AddSingleton<IHostedService>((services) => new BackgroundProcessWrapper(process));
return services;
}
}
} Alternative DesignsI'm sure there's a better way to do this - just starting the conversation on a way to achieve "minimal workers" if other folks think there's value. RisksWe'd need to support the ability to inject services from the Feedback welcome.
|
This should be a separate feature request. There's all sorts of questions here about scheduling.
Agree but I don't know what the fix is for that. Also, I'm not sure why the method takes a delegate? Is that to support DI parameters? What about scoped services? What about the cancellation token etc. etc. |
Here's the proposal for the new I'm on the fence about the |
I think I used the wrong term here, by recurring I meant the very same implementation that is in the proposed issue which run the delegate in a while loop, and the simple one just don't have the while loop.
Yes, the internal class RecurringBackgroundProcessWrapper : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly Func<IServiceProvider, CancellationToken, Task> _process;
public RecurringBackgroundProcessWrapper(IServiceProvider serviceProvider, Delegate process)
{
_process = BackgroundServiceBuilder.Build(process);
_serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var scope = _serviceScopeFactory.CreateScope();
try
{
await _process.Invoke(scope.ServiceProvider, stoppingToken);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
}
}
internal class BackgroundProcessWrapper : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly Func<IServiceProvider, CancellationToken, Task> _process;
public BackgroundProcessWrapper(IServiceProvider serviceProvider, Delegate process)
{
_process = BackgroundServiceBuilder.Build(process);
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// I'm not sure if this one is also needs a scope.
// It seems the Microsoft implementation of IServiceProvider can inject scope service
// when there's no scope but I'm not sure about the third party implementations.
await _process.Invoke(_serviceProvider, stoppingToken);
}
}
public static class BackgroundServiceExtensions
{
public static IServiceCollection AddBackgroundProcess(this IServiceCollection services, Delegate process)
{
services.AddSingleton<IHostedService>((services) => new BackgroundProcessWrapper(services, process));
return services;
}
public static IServiceCollection AddRecurringBackgroundProcess(this IServiceCollection services, Delegate process)
{
services.AddSingleton<IHostedService>((services) => new RecurringBackgroundProcessWrapper(services, process));
return services;
}
} |
So something that runs in a tight loop? Is that useful without being able to control an interval? Or is the idea that you also have to wait in your callback? |
Yes, this way the callback could even decide to have different interval if its needed. Another idea is that the delegate could return a |
I think we should overload Proposed API// Microsoft.Extensions.Hosting.Abstractions.dll
namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionHostedServiceExtensions
{
+ public static IServiceCollection AddHostedService(this IServiceCollection services, Func<IServiceProvider, CancellationToken> backgroundWorker)
} Usage Examplesvar builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<MyService>();
builder.Services.AddHostedService(async (serviceProvider, stoppingToken) =>
{
var myService = serviceProvider.GetRequiredService<MyService>();
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
service.Ping()
}
});
builder.Build().Run(); Alternative DesignsWe could pass in the + public static IServiceCollection AddHostedService(this IServiceCollection services, Func<IHost, CancellationToken> backgroundWorker) Or we could pass in a + public static IServiceCollection AddHostedService(this IServiceCollection services, Delegate backgroundWorker) Risks@Kahbazi points out an interesting risk above.
We could have the host register a parent hosted service by default that runs child hosted services added by an extension method on
|
Did you mean IHostBuilder? |
I think we should avoid this. There's too much infrastructure missing to do this properly and it will turn into a mini framework before we know it. We should do the basic overload and hold off on the Delegate overload. |
Background and Motivation
I had this idea over the weekend when building a minimal API, that would be augmented with a
BackgroundService
later on for some background processes. I thought "it would be great to have something like a Minimal Workers sort of API. I mentioned it to @halter73 in an email to get his 2C, but wanted to put this out here for discussion.Proposed API
This is in no way proposed to be final, but, hopefully the idea will come across. Let's say I have 3 different processes I want to run on intervals in the background of an ASP.NET app, but none of them are really big enough to warrant a whole new class. It'd be great if I could just do something like this in my
Program.cs
to wire up some background processes.Usage Examples
Very Brief Implementation and Usage
I wired up a super-simple implementation to support this usage scenario. This is in no way intended to be an actual proposal - surely it'd need some improvement and more consideration.
Alternative Designs
I'm sure there's a better way to do this - just starting the conversation on a way to achieve "minimal workers" if other folks think there's value.
Risks
We'd need to support the ability to inject services from the
IServicesCollection
into theAddBackgroundProcess
process
Func, so folks could use their services within theExecuteAsync
phase.Feedback welcome.
The text was updated successfully, but these errors were encountered: