using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.Concurrent;
namespace HostedServiceExample
public static void Main()
var builder = new HostBuilder()
.ConfigureAppConfiguration(confBuilder =>
.ConfigureLogging((configLogging) =>
configLogging.AddConsole();
configLogging.AddDebug();
.ConfigureServices((services) =>
services.AddHostedService<TaskSchedulerService>();
services.AddHostedService<WorkerService>();
services.AddSingleton<Settings>();
services.AddSingleton<TaskProcessor>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
public class TaskSchedulerService : IHostedService, IDisposable
private readonly IServiceProvider services;
private readonly Settings settings;
private readonly ILogger logger;
private readonly Random random = new Random();
private readonly object syncRoot = new object();
public TaskSchedulerService(IServiceProvider services)
this.services = services;
this.settings = services.GetRequiredService<Settings>();
this.logger = services.GetRequiredService<ILogger<TaskSchedulerService>>();
public Task StartAsync(CancellationToken cancellationToken)
var interval = settings?.RunInterval ?? 0;
logger.LogWarning("checkInterval is not defined in settings. Set to default: 60 sec.");
TimeSpan.FromSeconds(interval));
return Task.CompletedTask;
private void ProcessTask()
if (Monitor.TryEnter(syncRoot))
logger.LogInformation($"Process task started");
for (int i = 0; i < 20; i++) DoWork();
logger.LogInformation($"Process task finished");
logger.LogInformation($"Processing is currently in progress. Skipped");
var number = random.Next(20);
var processor = services.GetRequiredService<TaskProcessor>();
var queue = services.GetRequiredService<IBackgroundTaskQueue>();
queue.QueueBackgroundWorkItem(token =>
return processor.RunAsync(number, token);
public Task StopAsync(CancellationToken cancellationToken)
timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
private readonly IConfiguration configuration;
public Settings(IConfiguration configuration)
this.configuration = configuration;
public int WorkersCount => 1;
public int RunInterval => 5;
public string InstanceName => "Service";
public string ResultPath => "result.txt";
public class TaskProcessor
private readonly ILogger<TaskProcessor> logger;
private readonly Settings settings;
public TaskProcessor(ILogger<TaskProcessor> logger, Settings settings)
this.settings = settings;
public async Task RunAsync(int number, CancellationToken token)
token.ThrowIfCancellationRequested();
Func<int, int> fibonacci = null;
else return fibonacci(num - 1) + fibonacci(num - 2);
var result = await Task.Run(async () =>
return Enumerable.Range(0, number).Select(n => fibonacci(n));
using (var writer = new StreamWriter(settings.ResultPath, true, Encoding.UTF8))
writer.WriteLine(DateTime.Now.ToString() + " : " + string.Join(" ", result));
logger.LogInformation($"Task finished. Result: {string.Join(" ", result)}");
public class BackgroundTaskQueue : IBackgroundTaskQueue
private ConcurrentQueue<Func<CancellationToken, Task>> workItems = new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim signal = new SemaphoreSlim(0);
public int Size => workItems.Count;
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
await signal.WaitAsync(cancellationToken);
workItems.TryDequeue(out var workItem);
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
throw new ArgumentNullException(nameof(workItem));
workItems.Enqueue(workItem);
public interface IBackgroundTaskQueue
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken);
public static class HostExtensions
public static Task RunService(this IHostBuilder hostBuilder)
return hostBuilder.RunConsoleAsync();
public class WorkerService : BackgroundService
private readonly IBackgroundTaskQueue taskQueue;
private readonly ILogger<WorkerService> logger;
private readonly Settings settings;
public WorkerService(IBackgroundTaskQueue taskQueue, ILogger<WorkerService> logger, Settings settings)
this.taskQueue = taskQueue;
this.settings = settings;
protected override async Task ExecuteAsync(CancellationToken token)
var workersCount = settings.WorkersCount;
var workers = Enumerable.Range(0, workersCount).Select(num => RunInstance(num, token));
await Task.WhenAll(workers);
private async Task RunInstance(int num, CancellationToken token)
logger.LogInformation($"#{num} is starting.");
while(!token.IsCancellationRequested)
var workItem = await taskQueue.DequeueAsync(token);
logger.LogInformation($"#{num}: Processing task. Queue size: {taskQueue.Size}.");
logger.LogError(ex, $"#{num}: Error occurred executing task.");
logger.LogInformation($"#{num} is stopping.");