.net 核心`IHostedService` 是否应该启动一个新线程

mos*_*aka 4 async-await .net-core asp.net-core-hosted-services

.net coreBackgroundServiceIHostedService的 start 方法是异步的:

//IHostedService
Task StartAsync(CancellationToken cancellationToken);
//BackgroundService
Task ExecuteAsync(CancellationToken stoppingToken);
Run Code Online (Sandbox Code Playgroud)

那么我应该在ExecuteAsync/StartAsync方法中编写所有逻辑,还是应该启动一个新线程并立即返回?

例如,以下哪两个是正确的实现?

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
}
Run Code Online (Sandbox Code Playgroud)

2.

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        //actual works
        await Task.Delay(1000);//e.g
    }
}
Run Code Online (Sandbox Code Playgroud)

从语义上讲,我认为第二个似乎是正确的,但是如果有几个IHostedServices,它们可以与第二种形式并行运行吗?

编辑 1

我还编写了一个示例程序,说明托管服务本身不是作为单独的线程运行的。

"Waiting for signal.."我键入 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ary 6

只需使用async/实现它await,如您的第二个示例所示。不需要额外的Thread.

旁注:Thread应该只用于 COM 互操作;如今,对于Thread. 只要您输入new Thread,您就已经拥有遗留代码。

  • @CraigMiller 我建议你在说这个人不理解异步/等待和线程之前先查一下你在这里回复的人。 (4认同)
  • 这个答案显示了对 async/await 和线程的完全误解。仅具有 async 和 wait 并不能保证您的代码将异步运行,Thread 代码也不是过时的。在曾经使用过新线程的地方使用 Task.Run 已经变得更加常见,但它不允许对底层线程的所有功能进行所有访问。 (2认同)
  • @CraigMiller:我喜欢你的评论!确实,“async”并不一定意味着异步;而是意味着异步。由于操作使用“await Task.Delay”作为他们的工作示例,我假设他们有实际的异步工作要做。如果实现是同步的,我仍然建议`Task.Run`。我仍然相信“Thread”对于除 COM 互操作之外的所有场景来说都是过时的;调试时我确实会错过线程名称,但仅此而已。除了 COM 互操作之外,对于每个场景,都有比“Thread”更容易使用的更现代的解决方案。 (2认同)