.net core BackgroundService
or IHostedService
's start method is async:
//IHostedService
Task StartAsync(CancellationToken cancellationToken);
//BackgroundService
Task ExecuteAsync(CancellationToken stoppingToken);
So should I write all the logic in the ExecuteAsync
/StartAsync
method, or should I just start a new thread and return right away?
For example, which of the following two is the correct implementation?
1.
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
new Thread(async () => await DoWork(stoppingToken)).Start();
await Task.CompletedTask;
}
private async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
//actual works
}
2.
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//actual works
await Task.Delay(1000);//e.g
}
}
Semantically I think the second one seems to be right, but if there're several IHostedService
s, can they run in parallel with the second form?
Edit 1
I also write a sample program that illustrate the hosted services aren't themselves run as seperated threads.
The message "Waiting for signal.."
won't be written console until I type a q
:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace BackgroundTaskTest
{
public class Program
{
public static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
IConfiguration config = hostContext.Configuration;
//register tasks
services.AddHostedService<ReadService>();
services.AddHostedService<BlockService>();
})
.UseConsoleLifetime()
.Build();
await host.RunAsync();
}
}
public static class WaitClass
{
public static AutoResetEvent Event = new AutoResetEvent(false);
}
public class ReadService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var c = Console.ReadKey();
if (c.KeyChar == 'q')
{
Console.WriteLine("\nTrigger event");
WaitClass.Event.Set();
}
await Task.Delay(1);
}
}
}
public class BlockService : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("Waiting for signal..");
WaitClass.Event.WaitOne();
Console.WriteLine("Signal waited");
}
return Task.CompletedTask;
}
}
}
When you register implementations of IHostedService using any of the AddHostedService extension methods - the service is registered as a singleton.
The IHostedService interface provides a convenient way to start background tasks in an ASP.NET Core web application (in . NET Core 2.0 and later versions) or in any process/host (starting in . NET Core 2.1 with IHost ).
In ASP.NET Core, background tasks can be implemented as hosted services. A hosted service is a class with background task logic that implements the IHostedService interface. This article provides three hosted service examples: Background task that runs on a timer. Hosted service that activates a scoped service.
CreateDefaultBuilder(args) . ConfigureWebHostDefaults(webBuilder => { webBuilder. UseStartup<Startup>(); }); } A background service is a specific type that runs asynchronously within a console application host with the idea it does not interfere with the primary process.
Just implement it using async
/await
, as your second example shows. There is no need for an extra Thread
.
Side note: Thread
should only be used for COM interop; there are far better solutions these days for every other former use case of Thread
. As soon as you type new Thread
, you already have legacy code.
For StartAsync()
, at least, you should start a separate thread. If your job is going to take a significant length of time to complete. Otherwise -- at least in my experience, with .NET Core 2.1 on Windows 10 -- the IHostedService
won't register as "started" until all the work is done.
I have a test console app that HTTP-GETs the same URL 360 times, waiting 10 seconds between each, and counting how many times it succeeds and how many times it fails. In this app, I have configured ILogger
logging with NLog
, going both to a text file and to the console. Highest level of logging verbosity for each.
Once the hosted application finishes starting, I see a message logged to the console (and to the file), telling me that the hosted application has started, and that I can press Ctrl+C to quit.
If in StartAsync()
, I simply await
the test method that does all the work, then I don't see that message logged until all the work is done, over an hour later. If instead, I kick off a new thread as in your first example, then I see the Ctrl+C instructions almost immediately, as I should.
So empirically, your first example appears to be correct.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With