I'm trying to create a console application that run ahosted service with Simple Injector and I looked to the example at generichostintegration.
Now I would like to change IProcessor.DoSomeWork
to be an async
function with a cancellation token as parameter so DoSomeWork
can be canceled:
public async void DoSomeWork(CancellationToken cancellationToken)
{
await Task.Delay(_settings.Delay, cancellationToken);
}
How can i inject the cancellation token from HostedService
private void DoWork()
{
try
{
using (AsyncScopedLifestyle.BeginScope(this.container))
{
var service = this.container.GetInstance<TService>();
this.settings.Action(service);
}
}
catch (Exception ex)
{
this.logger.LogError(ex, ex.Message);
}
}
and configurate in the right way the container
container.RegisterInstance(new TimedHostedService<IProcessor>.Settings(
interval: TimeSpan.FromSeconds(10),
action: processor => processor.DoSomeWork()));
I'm a bit stack with that. Maybe i'm thinking wrong?
*** Updated ***
This is what I did in the end. I kept it as simple as possible.
class Program
{
public static async Task Main(string[] args)
{
var container = new Container();
IHost host = CreateHostBuilder(args, container)
.Build()
.UseSimpleInjector(container);
ConfigureContainer(container);
await host.RunAsync();
}
private static void ConfigureContainer(Container container)
{
container.Register<IWorkScheduler, WorkScheduler>(Lifestyle.Singleton);
// Sets the schedule timer interval
container.RegisterInstance(new WorkSchedulerSettings(TimeSpan.FromSeconds(1)));
container.Register<DoSomethingWorker>();
container.RegisterInstance(new DoSomethingSettings(new TimeSpan(0, 0, 5)));
container.Register<DoSomethingElseWorker>();
container.RegisterInstance(new DoSomethingElseSettings(new TimeSpan(0, 0, 10)));
container.Verify();
}
public static IHostBuilder CreateHostBuilder(string[] args, Container container) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddSimpleInjector(container, options =>
{
// Registers the hosted service as singleton in Simple Injector
// and hooks it onto the .NET Core Generic Host pipeline.
options.AddHostedService<BackgroundHostedService>();
services.AddLogging();
});
})
.UseConsoleLifetime();
}
the hosted service
public class BackgroundHostedService
: BackgroundService
{
private readonly IWorkScheduler _scheduler;
private readonly Container _container;
private readonly ILogger _logger;
public BackgroundHostedService(IWorkScheduler scheduler, Container container, ILogger<BackgroundHostedService> logger)
{
_scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler));
_container = container ?? throw new ArgumentNullException(nameof(container));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public override Task StartAsync(CancellationToken cancellationToken)
{
LoadWorkers();
return base.StartAsync(cancellationToken);
}
protected override Task ExecuteAsync(CancellationToken cancellationToken)
{
try
{
_scheduler.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
return Task.CompletedTask;
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
await _scheduler.Stop();
}
private void LoadWorkers()
{
// Hook up triggers and specify span period and if they have to run once at time.
WorkTrigger trigger1 = new WorkTrigger(_container.GetInstance<DoSomethingWorker>(), new TimeSpan(0, 0, 2), false);
_scheduler.AddTrigger(trigger1);
WorkTrigger trigger2 = new WorkTrigger(_container.GetInstance<DoSomethingElseWorker>(), new TimeSpan(0, 0, 5), true);
_scheduler.AddTrigger(trigger2);
}
public override void Dispose()
{
_scheduler.Dispose();
base.Dispose();
}
the "pseudo" scheduler
public interface IWorkScheduler : IDisposable
{
void Start();
Task Stop();
void AddTrigger(WorkTrigger trigger);
}
public class WorkSchedulerSettings
{
public readonly TimeSpan Interval;
public WorkSchedulerSettings(TimeSpan interval)
{
Interval = interval;
}
}
public class WorkScheduler
: IWorkScheduler, IDisposable
{
private readonly Timer _timer;
private readonly WorkSchedulerSettings _settings;
private readonly ILogger<WorkScheduler> _logger;
private readonly List<Task> _tasks;
private readonly List<WorkTrigger> _triggers;
private readonly CancellationTokenSource _cancTokenSource;
public WorkScheduler(WorkSchedulerSettings settings, ILogger<WorkScheduler> logger)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timer = new Timer(callback: _ => DoWork());
_tasks = new List<Task>();
_triggers = new List<WorkTrigger>();
_cancTokenSource = new CancellationTokenSource();
}
public void Start()
{
_logger.LogInformation("Scheduler started");
_timer.Change(dueTime: TimeSpan.Zero, period: _settings.Interval);
}
public async Task Stop()
{
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_cancTokenSource.Cancel();
await Task.WhenAll(_tasks);
_tasks.Clear();
_logger.LogInformation("Scheduler stopped");
}
public void AddTrigger(WorkTrigger trigger)
{
if (trigger == null) throw new ArgumentNullException(nameof(trigger));
_triggers.Add(trigger);
}
private void DoWork()
{
foreach (var trigger in _triggers)
{
if (trigger.CanExecute(DateTime.Now))
{
var task = trigger
.Execute(_cancTokenSource.Token)
.ContinueWith(x => HandleError(x));
_tasks.Add(task);
}
}
_tasks.RemoveAll(x => x.IsCompleted);
}
private void HandleError(Task task)
{
if (task.IsFaulted)
_logger.LogError(task.Exception.Message);
}
public void Dispose()
{
_timer?.Dispose();
_cancTokenSource?.Dispose();
}
}