.net core `IHostedService` 是否应该启动一个新线程
Should .net core `IHostedService` start a new thread
.net core BackgroundService
或IHostedService
的启动方式是async:
//IHostedService
Task StartAsync(CancellationToken cancellationToken);
//BackgroundService
Task ExecuteAsync(CancellationToken stoppingToken);
那么我是应该把所有的逻辑都写在ExecuteAsync
/StartAsync
方法里,还是直接开一个线程return就可以了?
例如,下面哪两个是正确的实现方式?
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
}
}
从语义上我认为第二种似乎是正确的,但是如果有几个IHostedService
,它们可以运行与第二种形式并行吗?
编辑 1
我还编写了一个示例程序来说明托管服务本身 运行 不是单独的线程。
在我输入 q
:
之前,消息 "Waiting for signal.."
不会写入控制台
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;
}
}
}
只需使用 async
/await
实现它,如第二个示例所示。不需要额外的 Thread
.
旁注:Thread
应仅用于 COM 互操作;如今,对于 Thread
的所有其他先前用例,都有更好的解决方案。只要您键入 new Thread
,您就已经拥有遗留代码。
对于StartAsync()
,至少,你应该开一个单独的线程。如果您的工作需要很长时间才能完成。否则——至少根据我的经验,在 Windows 10 上使用 .NET Core 2.1——在完成所有工作之前,IHostedService
不会注册为“已启动”。
我有一个 HTTP-GET 相同 URL 360 次的测试控制台应用程序,每次之间等待 10 秒,并计算成功次数和失败次数。在此应用程序中,我配置了 ILogger
使用 NLog
进行日志记录,同时转到文本文件和控制台。每个的最高级别的日志记录详细程度。
托管应用程序完成启动后,我看到一条消息记录到控制台(和文件),告诉我托管应用程序已经启动,我可以按 Ctrl+C 退出。
如果在 StartAsync()
中,我只是 await
完成所有工作的测试方法,那么在 所有工作完成之前我不会看到该消息记录,一个多小时后。 如果相反,我像在您的第一个示例中那样启动一个新线程,那么我应该几乎立即看到 Ctrl+C 指令。
根据经验,您的第一个示例似乎是正确的。
.net core BackgroundService
或IHostedService
的启动方式是async:
//IHostedService
Task StartAsync(CancellationToken cancellationToken);
//BackgroundService
Task ExecuteAsync(CancellationToken stoppingToken);
那么我是应该把所有的逻辑都写在ExecuteAsync
/StartAsync
方法里,还是直接开一个线程return就可以了?
例如,下面哪两个是正确的实现方式?
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
}
}
从语义上我认为第二种似乎是正确的,但是如果有几个IHostedService
,它们可以运行与第二种形式并行吗?
编辑 1
我还编写了一个示例程序来说明托管服务本身 运行 不是单独的线程。
在我输入 q
:
"Waiting for signal.."
不会写入控制台
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;
}
}
}
只需使用 async
/await
实现它,如第二个示例所示。不需要额外的 Thread
.
旁注:Thread
应仅用于 COM 互操作;如今,对于 Thread
的所有其他先前用例,都有更好的解决方案。只要您键入 new Thread
,您就已经拥有遗留代码。
对于StartAsync()
,至少,你应该开一个单独的线程。如果您的工作需要很长时间才能完成。否则——至少根据我的经验,在 Windows 10 上使用 .NET Core 2.1——在完成所有工作之前,IHostedService
不会注册为“已启动”。
我有一个 HTTP-GET 相同 URL 360 次的测试控制台应用程序,每次之间等待 10 秒,并计算成功次数和失败次数。在此应用程序中,我配置了 ILogger
使用 NLog
进行日志记录,同时转到文本文件和控制台。每个的最高级别的日志记录详细程度。
托管应用程序完成启动后,我看到一条消息记录到控制台(和文件),告诉我托管应用程序已经启动,我可以按 Ctrl+C 退出。
如果在 StartAsync()
中,我只是 await
完成所有工作的测试方法,那么在 所有工作完成之前我不会看到该消息记录,一个多小时后。 如果相反,我像在您的第一个示例中那样启动一个新线程,那么我应该几乎立即看到 Ctrl+C 指令。
根据经验,您的第一个示例似乎是正确的。