Search code examples
.net-coreasync-awaitasp.net-core-hosted-services

Should .net core `IHostedService` start a new thread


.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 IHostedServices, 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;
        }
    }
}

Solution

  • 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.