'Is it ok to use task.Run to parallel two backgroundservice in a Net6 Worker process?

I'm working with Net6 and Worker process (not asp.net). I need to have two or more backgroundservices running in parallel in the same worker process.

I do that with Task.Run(()=>...) but I don't know if it is a good practice and I would ask to you what do you think about it.

So I create a new Worker project with two hostedServices:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
            services
            .AddHostedService<Worker>()
            .AddHostedService<Worker2>();
        });

Backgroundservice Worker implementation:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

    for (int i = 0; i < 1000; i++)
    {
        if (stoppingToken.IsCancellationRequested) break;
        Console.WriteLine($"Worker1 - {i}");
    }
}

Worker2:

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

        for (int i = 0; i < 1000; i++)
        {
            if (stoppingToken.IsCancellationRequested) break;
            Console.WriteLine($"Worker2 - {i}");
        }
    }

I'll got as result

Worker1 - 1
Worker1 - 2
...
Worker1 - 999
Worker2 - 1
Worker2 - 2
...
Worker2 - 999

So the task Worker2 is executed after the Worker.

If I use Task.Run inside each ExecuteAsync:

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await Task.Run(() =>
            {
                _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

                for (int i = 0; i < 1000; i++)
                {
                    if (stoppingToken.IsCancellationRequested) break;
                    Console.WriteLine($"Worker1 - {i}");
                }

            });
        }

and

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Run(() =>
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);

            for (int i = 0; i < 1000; i++)
            {
                if (stoppingToken.IsCancellationRequested) break;
                Console.WriteLine($"Worker1 - {i}");
            }

        });
    }

The output is

Worker1 - 1
Worker2 - 1
Worker2 - 2
Worker1 - 1
...

Which to me is good but I'm asking to you if it is a good practice to use Task.Run to parallelize the two backgroundservices.

Somewhere I read something about it is a good practice avoid Task.Run. What else should I use then?

Thank you.



Solution 1:[1]

It depends but in general (if running process has enough resources to handle all of them) it is ok to run multiple hosted services inside one worker project.

Though your implementation has a big issue - ExecuteAsync methods are not actually async despite being marked those and will block the pipeline until they end executing. Quick fix would be adding await Task.Yield() to both of them to return control (you achieve similar effect via await Task.Run in second sample):

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
    await Task.Yield();
    // ....
}

Note the docs specifically mention that:

Avoid performing long, blocking initialization work in ExecuteAsync. The host blocks in StopAsync(CancellationToken) waiting for ExecuteAsync to complete.

Solution 2:[2]

You can check if the ExecuteAsync methods of multiple workers are executing synchronously and your task requires enough CPU computation then Task.Run can be used for parallelization

Also you don't have to make method async and wait for the CPU task to complete (via the await operator) if you don't need its result and/or handle exceptions.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2