From e18bd4b7bec60de7dfd642818d4d471d4eb91dc2 Mon Sep 17 00:00:00 2001 From: Farshad DASHTI Date: Wed, 2 Oct 2024 11:13:41 +0100 Subject: [PATCH] Added background service --- .../build-test-backgroundservice.yml | 19 ++++++ .github/workflows/pack-backgroundservice.yml | 15 +++++ DfE.CoreLibs.sln | 6 ++ .../DfE.CoreLibs.BackgroundService.csproj | 16 +++++ .../Interfaces/IBackgroundServiceEvent.cs | 8 +++ .../IBackgroundServiceEventHandler.cs | 8 +++ .../Interfaces/IBackgroundServiceFactory.cs | 8 +++ .../ServiceCollectionExtensions.cs | 16 +++++ .../Services/BackgroundServiceFactory.cs | 61 +++++++++++++++++++ 9 files changed, 157 insertions(+) create mode 100644 .github/workflows/build-test-backgroundservice.yml create mode 100644 .github/workflows/pack-backgroundservice.yml create mode 100644 src/DfE.CoreLibs.BackgroundService/DfE.CoreLibs.BackgroundService.csproj create mode 100644 src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEvent.cs create mode 100644 src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEventHandler.cs create mode 100644 src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceFactory.cs create mode 100644 src/DfE.CoreLibs.BackgroundService/ServiceCollectionExtensions.cs create mode 100644 src/DfE.CoreLibs.BackgroundService/Services/BackgroundServiceFactory.cs diff --git a/.github/workflows/build-test-backgroundservice.yml b/.github/workflows/build-test-backgroundservice.yml new file mode 100644 index 0000000..7893b63 --- /dev/null +++ b/.github/workflows/build-test-backgroundservice.yml @@ -0,0 +1,19 @@ +name: .NET Build and Test for DfE.CoreLibs.BackgroundService + +on: + push: + branches: + - main + paths: + - 'src/DfE.CoreLibs.BackgroundService/**' + +jobs: + build-and-test: + uses: ./.github/workflows/build-test-template.yml + with: + project_name: DfE.CoreLibs.BackgroundService + project_path: src/DfE.CoreLibs.BackgroundService + sonar_project_key: DFE-Digital_corelibs-backgroundService + secrets: + GITHUB__TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR__TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/pack-backgroundservice.yml b/.github/workflows/pack-backgroundservice.yml new file mode 100644 index 0000000..abab5f9 --- /dev/null +++ b/.github/workflows/pack-backgroundservice.yml @@ -0,0 +1,15 @@ +name: Build and Push NuGet Package for DfE.CoreLibs.BackgroundService + +on: + workflow_run: + workflows: [".NET Build and Test for DfE.CoreLibs.BackgroundService"] + types: + - completed + +jobs: + build-and-package: + uses: ./.github/workflows/nuget-package-template.yml + with: + project_name: DfE.CoreLibs.BackgroundService + project_path: src/DfE.CoreLibs.BackgroundService + nuget_package_name: DfE.CoreLibs.BackgroundService diff --git a/DfE.CoreLibs.sln b/DfE.CoreLibs.sln index 5273f06..4887d19 100644 --- a/DfE.CoreLibs.sln +++ b/DfE.CoreLibs.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.10.35122.118 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DfE.CoreLibs.Caching", "src\DfE.CoreLibs.Caching\DfE.CoreLibs.Caching.csproj", "{D88D58F0-18C4-4C3B-805B-8A483500E73E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DfE.CoreLibs.BackgroundService", "src\DfE.CoreLibs.BackgroundService\DfE.CoreLibs.BackgroundService.csproj", "{C7194A27-7254-49A3-8D0E-9B67708D3D43}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {D88D58F0-18C4-4C3B-805B-8A483500E73E}.Debug|Any CPU.Build.0 = Debug|Any CPU {D88D58F0-18C4-4C3B-805B-8A483500E73E}.Release|Any CPU.ActiveCfg = Release|Any CPU {D88D58F0-18C4-4C3B-805B-8A483500E73E}.Release|Any CPU.Build.0 = Release|Any CPU + {C7194A27-7254-49A3-8D0E-9B67708D3D43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7194A27-7254-49A3-8D0E-9B67708D3D43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7194A27-7254-49A3-8D0E-9B67708D3D43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7194A27-7254-49A3-8D0E-9B67708D3D43}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/DfE.CoreLibs.BackgroundService/DfE.CoreLibs.BackgroundService.csproj b/src/DfE.CoreLibs.BackgroundService/DfE.CoreLibs.BackgroundService.csproj new file mode 100644 index 0000000..234e055 --- /dev/null +++ b/src/DfE.CoreLibs.BackgroundService/DfE.CoreLibs.BackgroundService.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + + + + + + + + + + diff --git a/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEvent.cs b/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEvent.cs new file mode 100644 index 0000000..7c05647 --- /dev/null +++ b/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEvent.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace DfE.CoreLibs.BackgroundService.Interfaces +{ + public interface IBackgroundServiceEvent : INotification + { + } +} diff --git a/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEventHandler.cs b/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEventHandler.cs new file mode 100644 index 0000000..0294ef5 --- /dev/null +++ b/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceEventHandler.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace DfE.CoreLibs.BackgroundService.Interfaces +{ + public interface IBackgroundServiceEventHandler : INotificationHandler where TEvent : IBackgroundServiceEvent + { + } +} diff --git a/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceFactory.cs b/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceFactory.cs new file mode 100644 index 0000000..1217f7b --- /dev/null +++ b/src/DfE.CoreLibs.BackgroundService/Interfaces/IBackgroundServiceFactory.cs @@ -0,0 +1,8 @@ +namespace DfE.CoreLibs.BackgroundService.Interfaces +{ + public interface IBackgroundServiceFactory + { + void EnqueueTask(Func> taskFunc, Func? eventFactory = null) + where TEvent : IBackgroundServiceEvent; + } +} diff --git a/src/DfE.CoreLibs.BackgroundService/ServiceCollectionExtensions.cs b/src/DfE.CoreLibs.BackgroundService/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..7af6133 --- /dev/null +++ b/src/DfE.CoreLibs.BackgroundService/ServiceCollectionExtensions.cs @@ -0,0 +1,16 @@ +using DfE.CoreLibs.BackgroundService.Interfaces; +using DfE.CoreLibs.BackgroundService.Services; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddBackgroundService(this IServiceCollection services) + { + services.AddSingleton(); + services.AddHostedService(); + + return services; + } + } +} \ No newline at end of file diff --git a/src/DfE.CoreLibs.BackgroundService/Services/BackgroundServiceFactory.cs b/src/DfE.CoreLibs.BackgroundService/Services/BackgroundServiceFactory.cs new file mode 100644 index 0000000..866059f --- /dev/null +++ b/src/DfE.CoreLibs.BackgroundService/Services/BackgroundServiceFactory.cs @@ -0,0 +1,61 @@ +using System.Collections.Concurrent; +using DfE.CoreLibs.BackgroundService.Interfaces; +using MediatR; + +namespace DfE.CoreLibs.BackgroundService.Services +{ + public class BackgroundServiceFactory(IMediator mediator) : Microsoft.Extensions.Hosting.BackgroundService, IBackgroundServiceFactory + { + private readonly ConcurrentDictionary>> _taskQueues = new(); + private readonly ConcurrentDictionary _semaphores = new(); + + public void EnqueueTask(Func> taskFunc, Func? eventFactory = null) + where TEvent : IBackgroundServiceEvent + { + var taskType = taskFunc.GetType(); + var queue = _taskQueues.GetOrAdd(taskType, new ConcurrentQueue>()); + _semaphores.GetOrAdd(taskType, new SemaphoreSlim(1, 1)); + + queue.Enqueue(async () => + { + var result = await taskFunc(); + + if (eventFactory != null) + { + var taskCompletedEvent = eventFactory.Invoke(result); + await mediator.Publish(taskCompletedEvent); + } + }); + + _ = StartProcessingQueue(taskType); + } + + private async Task StartProcessingQueue(Type taskType) + { + var queue = _taskQueues[taskType]; + var semaphore = _semaphores[taskType]; + + await semaphore.WaitAsync(); + + try + { + while (queue.TryDequeue(out var taskToProcess)) + { + await taskToProcess(); + } + } + finally + { + semaphore.Release(); + } + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + await Task.Delay(1000, stoppingToken); + } + } + } +}