Multiple AddHostedService dotnet core - c#

When I try to register more than one AddHostedService the method StartAsync invokes only the first one.
services.AddHostedService<HostServiceBox>(); // StartAsync is called
services.AddHostedService<HostServiceWebSocket>(); // DO NOT WORK StartAsync not called
services.AddHostedService<HostServiceLogging>(); // DO NOT WORK StartAsync not called

Below a code who work
I get around the problem by creating a helper
#statup.cs
public void ConfigureServices(IServiceCollection services)
{
JwtBearerConfiguration(services);
services.AddCors(options => options.AddPolicy("CorsPolicy", builder =>
{
builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin()
.AllowCredentials();
}));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); ;
services.AddSignalR();
services.AddHostedService<HostServiceHelper>(); // <===== StartAsync is called
}
#HostServiceHelper.cs
public class HostServiceHelper : IHostedService
{
private static IHubContext<EngineHub> _hubContext;
public HostServiceHelper(IHubContext<EngineHub> hubContext)
{
_hubContext = hubContext;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
Task.Run(() => ServiceWebSocket(), cancellationToken);
Task.Run(() => ServiceBox(), cancellationToken);
Task.Run(() => ServiceLogging(), cancellationToken);
}, cancellationToken);
}
public void ServiceLogging()
{
// your own CODE
}
public void ServiceWebSocket()
{
// your own CODE
}
public void ServiceBox()
{
// your own CODE
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Your logical
throw new NotImplementedException();
}
}

OK, this is 2022 and .NET 6 is out. Now days it is not a problem to run multiple hosted services, as long as they are represented by different classes. Just like this:
public class Program
{
public static void Main(string[] args)
{
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.AddHostedService<Worker2>();
}).Build();
host.Run();
}
}
Both workers will run.
But what if we need multiple instances of the same service class to run parallel? That seems to be still impossible.
See relevant discussion here: https://github.com/dotnet/runtime/issues/38751
I ended up implementing my own utility function to start multiple tasks in parallel and collect all the exceptions properly. Here:
/// <summary>
/// Runs multiple cancelable tasks in parallel. If any of the tasks terminates, all others are cancelled.
/// </summary>
public static class TaskBunchRunner
{
public class BunchException : Exception
{
public AggregateException Agg { get; }
public BunchException(AggregateException agg) : base("Task bunch failed", agg)
{
Agg = agg;
}
public override string Message => $"Task bunch failed: {Agg.Message}";
public override string ToString() => $"BunchException -> {Agg.ToString()}";
}
public static async Task Bunch(this IEnumerable<Func<CancellationToken, Task>> taskFns, CancellationToken ct)
{
using CancellationTokenSource combinedTcs = CancellationTokenSource.CreateLinkedTokenSource(ct);
CancellationToken ct1 = combinedTcs.Token;
Task[] tasks = taskFns.Select(taskFn => Task.Run(() => taskFn(ct1), ct1)).ToArray();
// If any of the tasks terminated, it may be because of an error or a cancellation.
// In both cases we cancel all of them.
await Task.WhenAny(tasks); // this await will never throw
combinedTcs.Cancel();
var allTask = Task.WhenAll(tasks); // this will collect exceptions in an AggregateException
try
{
await allTask;
}
catch (Exception)
{
if (allTask.Exception != null) throw new BunchException(allTask.Exception);
throw;
}
// Why not just await Task.WhenAll() and let it throw whatever it is?
// Because await will unwrap the aggregated exception and rethrow just one of the inner exceptions,
// losing the information about others. We want all the exceptions to be logged, that is why
// we get the aggregated exception from the task. We also throw it wrapped into a custom exception, so the
// outer await (in the caller's scope) does not unwrap it again. :facepalm:
}
}
Now we create a single hosted service and make its ExecuteAsync method run several tasks as a bunch:
class MySingleService
{
private readonly string _id;
public MySingleService(string id){ _id = id; }
public async Task RunAsync(CancellationToken ct)
{
await Task.Delay(500, ct);
Console.WriteLine($"Message from service {_id}");
await Task.Delay(500, ct);
}
}
class MyHostedService: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
MySingleService[] individuals = new[]
{
new MySingleService("1"),
new MySingleService("2"),
new MySingleService("3"),
};
await individuals
.Select<MySingleService, Func<CancellationToken, Task>>(s => s.RunAsync)
.Bunch(stoppingToken);
}
}
public class Program
{
public static void Main(string[] args)
{
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<MyHostedService>();
}).Build();
host.Run();
}
}
Note 1: the TaskBunchRunner class was taken from a real project and proven to work, while the usage example is made up and not tested.
Note 2: The Bunch method was designed for background services, which do not naturally complete, they keep running until cancelled or failed. So if one of the tasks in a bunch successfully completes, others will be cancelled (which is probably not what you would want). If you need support for completion, I suggest checking the result of WhenAny: if the race winner has run to completion, we need to remove it from the array and WhenAny again.
I know it is not exactly what the OP asked for. But may be useful for someone who ends up here having the same problem as I had.

A hosted service is usually a single task so I'd do it with a singleton.
// Hosted Services
services.AddSingleton<IHostedService, HttpGetCurrencyPairRequestSyncingService>();
services.AddSingleton<IHostedService, HttpPostCurrencyPairRequestSyncingService>();
And when in my class,
public class CurrencyPairCacheManagementService : BaseHostedService<CurrencyPairCacheManagementService>
, ICurrencyPairCacheManagementService, IHostedService, IDisposable
{
private ICurrencyPairService _currencyPairService;
private IConnectionMultiplexer _connectionMultiplexer;
public CurrencyPairCacheManagementService(IConnectionMultiplexer connectionMultiplexer,
IServiceProvider serviceProvider) : base(serviceProvider)
{
_currencyPairService = serviceProvider.GetService<CurrencyPairService>();
_connectionMultiplexer = connectionMultiplexer;
InitializeCache(serviceProvider);
}
/// <summary>
/// Operation Procedure for CurrencyPair Cache Management.
///
/// Updates every 5 seconds.
///
/// Objectives:
/// 1. Pull the latest currency pair dataset from cold storage (DB)
/// 2. Cross reference checking (MemoryCache vs Cold Storage)
/// 3. Update Currency pairs
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("CurrencyPairCacheManagementService is starting.");
stoppingToken.Register(() => _logger.LogInformation("CurrencyPairCacheManagementService is stopping."));
while (!stoppingToken.IsCancellationRequested)
{
var currencyPairs = _currencyPairService.GetAllActive();
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
_logger.LogWarning("CurrencyPairCacheManagementService background task is stopping.");
}
public void InitializeCache(IServiceProvider serviceProvider)
{
var currencyPairs = _currencyPairService.GetAllActive();
// Load them individually to the cache.
// This way, we won't have to update the entire collection if we were to remove, update or add one.
foreach (var cPair in currencyPairs)
{
// Naming convention => PREFIX + CURRENCYPAIRID
// Set the object into the cache
}
}
public Task InproPair(CurrencyPair currencyPair)
{
throw new NotImplementedException();
}
}
ExecuteAsync gets hit first, before carrying on with what you want it to do. You might also want to remove the generics declaration I have because my Base class runs with generics (If you don't run your hosted service base class with generics then I don't think you'll need to inherit IHostedService and IDisposable explicitly).

Related

Simulating CancellationToken.IsCancellationRequested when unit testing

I would like to test a task that is supposed to run continuously until killed. Suppose the following method is being tested:
public class Worker
{
public async Task Run(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
// do something like claim a resource
}
catch (Exception e)
{
// catch exceptions and print to the log
}
finally
{
// release the resource
}
}
}
}
And a test case
[TestCase]
public async System.Threading.Tasks.Task Run_ShallAlwaysReleaseResources()
{
// Act
await domainStateSerializationWorker.Run(new CancellationToken());
// Assert
// assert that resource release has been called
}
The problem is that the task never terminates, because cancellation is never requested. Ultimately I would like to create a CancellationToken stub like MockRepository.GenerateStub<CancellationToken>() and tell it on which call to IsCancellationRequested return true, but CancellationToken is not a reference type so it is not possible.
So the question is how to make a test where Run executes for n iterations and then terminates? Is it possible without refactoring Run?
This depends on what is running within Run. If there is some injected dependency
For example
public interface IDependency {
Task DoSomething();
}
public class Worker {
private readonly IDependency dependency;
public Worker(IDependency dependency) {
this.dependency = dependency;
}
public async Task Run(CancellationToken cancellationToken) {
while (!cancellationToken.IsCancellationRequested) {
try {
// do something like claim a resource
await dependency.DoSomething();
} catch (Exception e) {
// catch exceptions and print to the log
} finally {
// release the resource
}
}
}
}
Then that can be mocked and monitored to count how many times some member has been invoked.
[TestClass]
public class WorkerTests {
[TestMethod]
public async Task Sohuld_Cancel_Run() {
//Arrange
int expectedCount = 5;
int count = 0;
CancellationTokenSource cts = new CancellationTokenSource();
var mock = new Mock<IDependency>();
mock.Setup(_ => _.DoSomething())
.Callback(() => {
count++;
if (count == expectedCount)
cts.Cancel();
})
.Returns(() => Task.FromResult<object>(null));
var worker = new Worker(mock.Object);
//Act
await worker.Run(cts.Token);
//Assert
mock.Verify(_ => _.DoSomething(), Times.Exactly(expectedCount));
}
}
The best you can do without changing your code is cancelling after a specific amount of time. The CancellationTokenSource.CancelAfter() method makes this easy:
[TestCase]
public async System.Threading.Tasks.Task Run_ShallAlwaysReleaseResources()
{
// Signal cancellation after 5 seconds
var cts = new TestCancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
// Act
await domainStateSerializationWorker.Run(cts.Token);
// Assert
// assert that resource release has been called
}
The way your code is written (checking IsCancellationRequested only once per iteration) means that the cancellation will happen after some number of complete iterations. It just won't be the same number each time.
If you want to cancel after a specific number of iterations, then your only option is to modify your code to keep track of how many iterations have happened.
I thought I might be able to create a new class that inherits from CancellationTokenSource to keep track of how many times IsCancellationRequested has been tested, but it's just not possible to do.

Topshelf start stuck in infinite loop

I'm creating a message processor to take messages of a queue
I have used topshelf for this and justgot some basic code for now. However my message processor is stuck in a loop and causing my topshelf service to not start. I thought if I returned and stored the task, this would not be the case
class Program
{
static void Main(string[] args)
{
HostFactory.Run(configure =>
{
configure.Service<WorkerService>(service =>
{
service.ConstructUsing(() => new WorkerService());
service.WhenStarted(s => s.Start());
service.WhenStopped(s => s.Stop());
});
configure.RunAsLocalSystem();
});
}
}
public class WorkerService
{
private Task _task;
private Processor _processor;
private readonly CancellationTokenSource _cancellation;
public WorkerService()
{
_cancellation = new CancellationTokenSource();
_processor = new Processor();
}
public void Start()
{
Console.WriteLine("Starting");
_task = _processor.Run(_cancellation.Token);
Console.WriteLine("I NEVER GET HERE");
}
public void Stop()
{
_cancellation.Cancel();
_task.Wait();
}
}
public class Processor
{
public async Task Run(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Running");
}
}
}
So when I look at my windows services I just see this app stuck in "Starting"
Your run function doesn't actually hit an await call (where it will exit and later resume). In fact, it doesn't exit at all. You're stuck in the Run method. Try putting this after your Console.WriteLine:
await Task.Delay(200);
In addition, you might consider not mixing async/await with traditional Task.Wait(), as that's known to cause deadlocks as well. Example: await vs Task.Wait - Deadlock?

.Net core IHostedService Background task exception will not terminate application

I have a program that needs to terminate when an IHostedService background task encounters a certain scenario. I was hoping to do this by just throwing an exception in the background task that would get kicked up to the main function. I could then trigger the cancellation token to kill other background tasks.
My problem is that when I throw the exception, it kills the task and that's all. Everything else keeps running. Is there a way to do this, or a better way to do what I'm trying to do? Is there another way to have a backgrounds task trigger the common CancellationToken?
I included a simplified version of my issue in the code below.
If I comment out the await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken); line, the exception does what I want and I can trigger the CancellationToken. When it is in place, the task stops, but the program does not.
NOTE: In my messier code I have more IHostedServices running, which is why I'm trying to trigger cancelSource.Cancel()
public class Program
{
public static async Task Main(string[] args)
{
using (var cancelSource = new CancellationTokenSource())
{
try
{
await new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<TestService>();
})
.Build()
.RunAsync(cancelSource.Token);
}
catch (Exception E)
{
cancelSource.Cancel();
}
}
}
}
public class TestService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
Console.WriteLine("loop 1");
throw new ApplicationException("OOPS!!");
}
}
}
Commenting the only line in the ExecuteAsync method with await operator makes your code run synchronously. If we look at the sources of BackgroundService.StartAsync, we can see that it checks for _executingTask.IsCompleted and it returns task that will contain your exception in case we don't have any await in ExecuteAsync method, otherwise it will return Task.CompletedTask and you won't be able to catch this exception from ExecuteAsync in Main method.
You can manage your services with IApplicationLifetime that can be injected in all your background services. For example, you can catch exception within ExecuteMethod and call ApplicationLifetime.StopApplication.
Example:
static async Task Main(string[] args)
{
await new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<TestService>();
services.AddHostedService<TestService2>();
})
.Build()
.RunAsync();
Console.WriteLine("App stoped");
}
Service 1
public class TestService : BackgroundService
{
private readonly IApplicationLifetime _applicationLifetime;
public TestService(IApplicationLifetime applicationLifetime)
{
_applicationLifetime = applicationLifetime;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!_applicationLifetime.ApplicationStopping.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(1), _applicationLifetime.ApplicationStopping);
Console.WriteLine("running service 1");
throw new ApplicationException("OOPS!!");
}
}
catch (ApplicationException)
{
_applicationLifetime.StopApplication();
}
}
}
Service 2
public class TestService2 : BackgroundService
{
private readonly IApplicationLifetime _applicationLifetime;
public TestService2(IApplicationLifetime applicationLifetime)
{
_applicationLifetime = applicationLifetime;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!_applicationLifetime.ApplicationStopping.IsCancellationRequested)
{
await Task.Delay(100, _applicationLifetime.ApplicationStopping);
Console.WriteLine("running service 2");
}
}
catch (ApplicationException)
{
_applicationLifetime.StopApplication();
}
}
}
Output:
running service 2
running service 2
running service 1
App stoped

Log configuration changes in ASP.NET Core

I want to log when configuration is changed.
I do this in Program.cs or Startup.cs:
ChangeToken.OnChange(
() => configuration.GetReloadToken(),
state => logger.Information("Configuration reloaded"),
(object)null
);
But I get double change reports, so it needs to be debounced. The advice is to do this:
ChangeToken.OnChange(
() => configuration.GetReloadToken(),
state => { Thread.Sleep(2000); logger.Information("Configuration reloaded"); },
(object)null
);
I'm using 2000 here as I'm not sure what's a reasonable value.
I've found that sometimes I still get multiple change detections, separated by 2000 milliseconds. So the debounce doesn't work for me, just causes a delay between reported changes. If I set a high value then I only get one report, but that isn't ideal (and conceals the problem).
So I'd like to know:
Is this really debouncing, or just queueing reported changes?
I've used values from 1000 to 5000 to varying success. What are others using?
Is the sleep issued to the server's main thread? I hope not!
The multiple change detection issue discussed here (and at least a dozen other issues in multiple repos) is something they refuse to address using a built-in mechanism.
The MS docs use a file hashing approach, but I think that debouncing is better.
My solution uses async (avoids async-in-sync which could blow up something accidentally) and a hosted service that debounces change detections.
Debouncer.cs:
public sealed class Debouncer : IDisposable {
public Debouncer(TimeSpan? delay) => _delay = delay ?? TimeSpan.FromSeconds(2);
private readonly TimeSpan _delay;
private CancellationTokenSource? previousCancellationToken = null;
public async Task Debounce(Action action) {
_ = action ?? throw new ArgumentNullException(nameof(action));
Cancel();
previousCancellationToken = new CancellationTokenSource();
try {
await Task.Delay(_delay, previousCancellationToken.Token);
await Task.Run(action, previousCancellationToken.Token);
}
catch (TaskCanceledException) { } // can swallow exception as nothing more to do if task cancelled
}
public void Cancel() {
if (previousCancellationToken != null) {
previousCancellationToken.Cancel();
previousCancellationToken.Dispose();
}
}
public void Dispose() => Cancel();
}
ConfigWatcher.cs:
public sealed class ConfigWatcher : IHostedService, IDisposable {
public ConfigWatcher(IServiceScopeFactory scopeFactory, ILogger<ConfigWatcher> logger) {
_scopeFactory = scopeFactory;
_logger = logger;
}
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<ConfigWatcher> _logger;
private readonly Debouncer _debouncer = new(TimeSpan.FromSeconds(2));
private void OnConfigurationReloaded() {
_logger.LogInformation("Configuration reloaded");
// ... can do more stuff here, e.g. validate config
}
public Task StartAsync(CancellationToken cancellationToken) {
ChangeToken.OnChange(
() => { // resolve config from scope rather than ctor injection, in case it changes (this hosted service is a singleton)
using var scope = _scopeFactory.CreateScope();
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
return configuration.GetReloadToken();
},
async () => await _debouncer.Debounce(OnConfigurationReloaded)
);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public void Dispose() => _debouncer.Dispose();
}
Startup.cs:
services.AddHostedService<ConfigWatcher>(); // registered as singleton
Hopefully, someone else can answer your questions, but I did run into this issue and found this Gist by cocowalla.
The code provided by cocowalla debounces instead of just waiting. It successfully deduplicated the change callback for me.
Cocowalla also includes an extension method so you can simply call OnChange on the IConfiguration.
Here's a sample:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
class Program
{
public static async Task Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(path: "appsettings.json", optional: false, reloadOnChange: true)
.Build();
configuration.OnChange(() => Console.WriteLine("configuration changed"));
while (true)
{
await Task.Delay(1000);
}
}
}
public class Debouncer : IDisposable
{
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private readonly TimeSpan waitTime;
private int counter;
public Debouncer(TimeSpan? waitTime = null)
{
this.waitTime = waitTime ?? TimeSpan.FromSeconds(3);
}
public void Debouce(Action action)
{
var current = Interlocked.Increment(ref this.counter);
Task.Delay(this.waitTime).ContinueWith(task =>
{
// Is this the last task that was queued?
if (current == this.counter && !this.cts.IsCancellationRequested)
action();
task.Dispose();
}, this.cts.Token);
}
public void Dispose()
{
this.cts.Cancel();
}
}
public static class IConfigurationExtensions
{
/// <summary>
/// Perform an action when configuration changes. Note this requires config sources to be added with
/// `reloadOnChange` enabled
/// </summary>
/// <param name="config">Configuration to watch for changes</param>
/// <param name="action">Action to perform when <paramref name="config"/> is changed</param>
public static void OnChange(this IConfiguration config, Action action)
{
// IConfiguration's change detection is based on FileSystemWatcher, which will fire multiple change
// events for each change - Microsoft's code is buggy in that it doesn't bother to debounce/dedupe
// https://github.com/aspnet/AspNetCore/issues/2542
var debouncer = new Debouncer(TimeSpan.FromSeconds(3));
ChangeToken.OnChange<object>(config.GetReloadToken, _ => debouncer.Debouce(action), null);
}
}
In the sample, the debounce delay is 3 seconds, for my small json file, the debounce delay stops deduplicating around 230 milliseconds.

How do i call a method from a singleton service to run throughout the application lifetime

I have implemented a Kafka event bus as a singleton service in Net Core. The service itself is configured with Autofac in Startup.cs. The service has a Listen() method:
public void Listen()
{
using(var consumer = new Consumer<Null, string>(_config, null, new StringDeserializer(Encoding.UTF8)))
{
consumer.Subscribe(new string[] { "business-write-topic" });
consumer.OnMessage += (_, msg) =>
{
Console.WriteLine($"Topic: {msg.Topic} Partition: {msg.Partition} Offset: {msg.Offset} {msg.Value}");
consumer.CommitAsync(msg);
};
while (true)
{
consumer.Poll(100);
}
}
}
My understanding is that in order for this method to constantly listen for messages during the lifetime of the application, i have to call it in Program.cs from the web host by somehow getting the ServiceProvider associated with the host, then retrieving an instance of the service, and calling the method.
I have configured my Program.cs from the default Net Core 2.1 template to the following:
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHost(args);
host.Run();
}
public static IWebHost CreateWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
Beyond having the host available so i can somehow access the services, i don't know where to go from here. I have searched for similar questions and read around in the official docs but i can't seem to figure out how to access the service so that i can call the Listen() method.
Is this the "go-to" way of accomplishing my goal? If so, how do i proceed? And if not - that is - if this kind of task is commonly accomplished in another way, how do i go about it?
Edit:
The answer below is still perfectly valid. There is a base-class called BackgroundService provided by Microsoft that can be used where you only need to implement ExecuteAsync(CancellationToken stoppingToken) rather than the whole interface of IHostedService. You can find it here. For that you will need to install the package Microsoft.Extensions.Hosting.
Previous and still valid answer:
I would suggest to use IHostedService. IHostedService implementations are registered as singletons and they run the whole time until the server shuts down.
Create this base class
public abstract class HostedService : IHostedService
{
private Task executingTask;
private CancellationTokenSource cancellationTokenSource;
public Task StartAsync(CancellationToken cancellationToken)
{
this.cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
this.executingTask = this.ExecuteAsync(this.cancellationTokenSource.Token);
return this.executingTask.IsCompleted ? this.executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (this.executingTask == null)
{
return;
}
this.cancellationTokenSource.Cancel();
await Task.WhenAny(this.executingTask, Task.Delay(-1, cancellationToken));
}
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
Then create the consumer-host
public class ConsumerHost : HostedService
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
using (var consumer = new Consumer<Null, string>(_config, null, new StringDeserializer(Encoding.UTF8)))
{
consumer.Subscribe(new string[] {"business-write-topic"});
consumer.OnMessage += (_, msg) =>
{
Console.WriteLine(
$"Topic: {msg.Topic} Partition: {msg.Partition} Offset: {msg.Offset} {msg.Value}");
consumer.CommitAsync(msg);
};
while (!cancellationToken.IsCancellationRequested) // will make sure to stop if the application is being shut down!
{
consumer.Poll(100);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
}
}
}
}
Now in your startup-class in the ConfigureService method add the singleton
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHostedService, ConsumerHost>();
}
This service will now kick in when the webhost finished building and stop when you shutdown the server. No need to manually trigger it, let the webhost do it for you.
I think BackgroundService is what you need.
public class ListnerBackgroundService : BackgroundService
{
private readonly ListnerService service;
public ListnerBackgroundService(ListnerService service)
{
this.service = service;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
service.Listen();
return Task.CompletedTask;
}
}
And register it:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IHostedService, ListnerBackgroundService>();
...
}

Categories

Resources