How to run multiple BackgroundService parallel in .net core 3.0? - c#

How is it possible to run multiple IHostedServices in parallel?
I use the WorkerService in .Net Core 3.0 and want both services to run parallel. Currently the second service is waiting for the first one to finish. Both services should run endlessly.
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<ServiceA>();
services.AddHostedService<ServiceB>();
});
}
A service looks like this:
public class ServiceA : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
do
{
Console.WriteLine("Sample");
await Task.Delay(5000, stoppingToken);
} while (!stoppingToken.IsCancellationRequested);
}
}
// edit:
Very reluctantly I would use a Task.Run(() => method()); method like this. But of course this way always works:
public class ServiceA : BackgroundService
{
public override Task StartAsync(CancellationToken cancellationToken)
{
Task.Factory.StartNew(() => ExecuteAsync(cancellationToken), cancellationToken);
return Task.CompletedTask;
}
}

I asked myself a similar question and made some search but couldn't find a good answer.
I solved the issue running every background service in Task.Run with a cancellation token from BackgroundService.ExecuteAsync()
I have 2 services like you.
public class BackgroundService1: BackgroundService
{
public BackgroundService1()
{
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Task.Run(async () =>
{
await DoWork(stoppingToken);
}, stoppingToken);
return Task.CompletedTask;
}
}
//Second service is just like the first one:
public class BackgroundService2: BackgroundService
{
public BackgroundService2()
{
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Task.Run(async () =>
{
await DoWork(stoppingToken);
}, stoppingToken);
return Task.CompletedTask;
}
}
and register them in Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<BackgroundService1>();
services.AddHostedService<BackgroundService2>();
})
.UseWindowsService()

I've had the same kind of issue: Multiple service that do different work at different frequencies.
When looking into it, BackgroundService seems to be designed for sequential execution (an infinite loop based service's worst enemy).
After getting a hint from this thread, I found the solution that works for my case using Microsoft's Timer Service example.
The base TimerService implements IHostedService and IAsyncDisposable:
StartAsync() starts the timer on the DoWork()
DoWork() is your overridable main work procedure.
StopAsync() stops the timer gracefully.
DisposeAsync() cleans up.
I've tested by deriving multiple TimerServices with different execution frequencies and adding them with services.AddHostedService<>();.
They all start and run at the same time, do their bit on clock.
/!\ It is not Task based as it uses timer events. Just pointing this out because I've already had quite a difficult troubleshooting experience the one time I mixed time-based events and Tasks /!\

No need to manually create a task. The default StartAsync calls ExecuteAsync and returns that task to be awaited somewhere else.
https://github.com/aspnet/Hosting/blob/master/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs#L30
So, you can do return base.StartAsync(cancellationToken) before returning Task.Completed in StartAsync.

Related

How to inject an IEnumerable<Interface> which is also a hosted service as singleton lifetime

I am looking to leverage off .Net Core taking care of the lifetime of services which are the same as the whole app and I want to inject them into another hosted service. I know that I could just implement the start/stop of the listeners myself in the Server's start/stop. But it feels unnecessary if I could the below scenario to work.
I would prefer to get it down to a single line for the registration. I was playing around with creating Extension methods to the IServiceCollection.
public class Server: IHostedService
{
public Server(IEnumerable<IConnectionListener> listeners)
{
foreach(var l in listeners)
{
l.Connected += HandleConnection;
}
}
private void HandleConnection(object src, Foo foo) { }
public async Task StartAsync(CancellationToken ct)
{}
public async Task StopAsync(CancellationToken ct)
{}
}
public interface IConnectionListener
{
event ConnectionHandler Connected;
}
public class ConnectionListener: BackgroundService, IConnectionListener
{
public async Task ExecuteAsync(CancellationToken ct)
{
// Open TcpListener and register with the ct to stop the listener.
}
}
public class SslConnectionListener: BackgroundService, IConnectionListener
{
public async Task ExecuteAsync(CancellationToken ct)
{
// Open TcpListener and register with the ct to stop the listener.
// Add some extra SSL stuff
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hc, services) =>
{
// This appears to work. But I have concerns about whether the life times will truly
// be singleton and automatic disposal of the objects (having used the factory, I do
// want the automatic disposal by the container).
var listener = new ConnectionListener();
var sslListener = new SslConnectionListener();
services.AddSingleton<IConnectionListener>(sp => listener);
services.AddSingleton<IConnectionListner>(sp => sslListener);
services.AddHostedService(sp => listener);
services.AddHostedService(sp => sslListener);
// This doesn't work
services.AddHostedService<SslConnectionListener>();
services.AddHostedService<ConnectionListener>()
services.AddHostedService<Server>();
}
You could consider this approach
//register the instances
services.AddSingleton<ConnectionListener>();
services.AddSingleton<SslConnectionListener>();
//register their abstractions
services.AddSingleton<IConnectionListener>(sp => sp.GetService<ConnectionListener>());
services.AddSingleton<IConnectionListener>(sp => sp.GetService<SslConnectionListener>());
//register hosted services.
services.AddHostedService(sp => sp.GetService<ConnectionListener>());
services.AddHostedService(sp => sp.GetService<SslConnectionListener>());
services.AddHostedService<Server>();
This way the container manages the entire lifetime of the created instances.
This could be neatly wrapped in an extension method to simplify the call.
The container is not creating this instance:
var listener = new ConnectionListener();
If you want it as a singleton, just use the resolver because you're already at the root:
services.AddSingleton<IConnectionListener, ConnectionListener>();
services.AddHostedService(resolver => { return resolver.GetService<IConnectionListener>(); });
Guaranteed to be a singleton

Why doesn't the .NET Generic Host stop when used with WinUI3?

I'm writing a WinUI3 (Project Reunion 0.5) application with .NET 5 and would like to use the .NET Generic Host. I'm using the default host with a custom IHostedService:
public App() {
_host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddHostedService<MyHostedService>();
}).Build();
InitializeComponent();
}
The hosted service performs some asynchronous operations in StopAsync. For demonstration purposes, let's say it delays for 1 second (this code still produces the issue):
public override async Task StopAsync(CancellationToken cancellationToken)
{
await Task.Delay(1000);
}
I start the host in OnLaunched:
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
await _host.StartAsync();
m_window = new MainWindow();
m_window.Activate();
}
I let the default ConsoleLifetime implementation stop the host before the process exits.
The Task returned by my IHostedService.StopAsync implementation completes, but IHost.StopAsync never returns and the process hangs with this message in the output:
Microsoft.Hosting.Lifetime: Information: Application is shutting down...
Microsoft.Hosting.Lifetime: Information: Waiting for the host to be disposed. Ensure all 'IHost' instances are wrapped in 'using' blocks.
If I step through with the debugger, sometimes the IHost.StopAsync method will time out and an exception will be thrown. This never happens outside of the debugger. I have tried explicitly stopping and disposing the host when the MainWindow is closed, but it didn't make any difference.
I thought perhaps the DispatcherQueueSynchronizationContext was being shut down before the host could stop and tasks were not being serviced, but the DispatcherQueue.ShutdownStarting event is never fired.
Any other ideas?
I took #Dai's advice from the comments and investigated running WinUI on a separate thread and running the host on the main thread.
I created an IHostedService to manage the WinUI application:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.System;
using Microsoft.UI.Xaml;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MyApp.Hosting
{
public class WinUIHostedService<TApplication> : IHostedService, IDisposable
where TApplication : Application, new()
{
private readonly IHostApplicationLifetime HostApplicationLifetime;
private readonly IServiceProvider ServiceProvider;
public WinUIHostedService(
IHostApplicationLifetime hostApplicationLifetime,
IServiceProvider serviceProvider)
{
HostApplicationLifetime = hostApplicationLifetime;
ServiceProvider = serviceProvider;
}
public void Dispose()
{
}
public Task StartAsync(CancellationToken cancellationToken)
{
var thread = new Thread(Main);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private void Main()
{
WinRT.ComWrappersSupport.InitializeComWrappers();
Application.Start((p) => {
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new TApplication();
});
HostApplicationLifetime.StopApplication();
}
}
}
I defined DISABLE_XAML_GENERATED_MAIN in the build settings and added my own Main:
public class Program
{
public static void Main(string[] args)
{
Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddHostedService<WinUIHostedService<App>>();
})
.Build().Run();
}
}
Voila! The WinUI application still runs fine and the host stops cleanly when the main window closes, even when IHostedService.StopAsync runs asynchronous code.
Note that this code is just the first thing that worked. It could probably be improved and I don't fully understand the Generic Host lifetime semantics.

How to implement parallel long running background tasks in .NET Core 3?

I have .NET Core console application containing two independent tasks that should be running in parallel for the entire life-time of the application. I was thinking to use BackgroundService:
class BackgroundTaskOne : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do long running task for the entire life-time of application
while(true)
{
// do work one
}
}
catch (Exception e)
{
// log
}
}
return Task.CompletedTask;
}
}
class BackgroundTaskTwo : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do long running task for the entire life-time of application
while(true)
{
// do work two
}
}
catch (Exception e)
{
// log
}
}
return Task.CompletedTask;
}
}
And register them like this:
services.AddHostedService<BackgroundTaskOne>();
services.AddHostedService<BackgroundTaskTwo>();
But these are going to run in order. So I have two questions:
Is there a way to make these two run in parallel?
Are there any other alternatives to run two long-running background processes in .NET Core in parallel?
The docs of BackgroundService.ExecuteAsync say
The implementation should return a task that represents the lifetime of the long running operation(s) being performed.
Your implementation returns a completed task when the whole work is done. In fact you implemented it to run sync and not async and that is the reason for not running parallel.
Here is a sample implementation with some fake async work:
class BackgroundTaskOne : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// do work one
await Task.Delay( 100 );
}
catch (Exception e)
{
// log
}
}
}
}
As documentation says:
https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/fundamentals/host/hosted-services.md
No further services are started until ExecuteAsync becomes
asynchronous, such as by calling await.
As long as you do not have async clause mentioned at the ExecuteAsync method above, I suspect your method is synchronous at whole. This is why two services are called sequentially, not in parallel. Give them a break, introduce good amount of awaitable code.

IOptonsMonitor.OnChange and async listener in ASP.NET Core

I have a class registered as a singleton. In the constructor of this class, I subscribe to IOptonsMonitor.OnChange:
class Test
{
public Test(IOptionsMonitor<MyOptions> myOptionsMonitor)
{
myOptionsMonitor.OnChange(async options => await SomeWork());
}
public async Task SomeWork()
{
await Task.Delay(TimeSpan.FromSeconds(30));
}
}
In this case SomeWork task will actually "Fire and Forgot" which is probably wrong. Another approach is to run SomeWork synchronously:
myOptionsMonitor.OnChange(options => SomeWork().GetAwaiter().GetResult());
But in this case, OnChange will be blocked for a time of SomeWork execution.
Which approach will be more correct?

Background tasks are being queued and not executed

I've implemented the BackgroundQueue as explained here, and as shown:
public ActionResult SomeAction()
{
backgroundQueue.QueueBackgroundWorkItem(async ct =>
{
//Do some work...
});
return Ok();
}
I registered the BackgroundQueue with Autofac as:
builder.RegisterType<BackgroundQueue>()
.As<IBackgroundQueue>()
.SingleInstance();
So far so good. I call my controller action and the task is added to the queue. And there it stays without being executed.
So how do I get the task to execute?
The BackgroundQueue implementation that you took from the documentation is only one part to the solution: The background queue will just keep track of the jobs that you want to be executed.
What you will also need is right below that in the docs: The QueuedHostedService. This is a background service that gets registered with the DI container and is started when the application starts. From then on, it will monitor your BackgroundQueue and work off jobs as they get queued.
A simplified example implementation of this background service, without logging or error handling, could look like this:
public class QueuedHostedService : BackgroundService
{
private readonly IBackgroundQueue _backgroundQueue;
public QueuedHostedService(IBackgroundQueue backgroundQueue)
{
_backgroundQueue = backgroundQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await _backgroundQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}

Categories