谈谈.NET Core中基于Generic Host来实现后台任务

谈谈.NET Core中基于Generic Host来实现后台任务,第1张

概述目录前言什么是Generic Host后台任务示例控制台形式消费MQ消息的后台任务Web形式部署IHostedService和BackgroundService的区别IHostBuilder的扩展写法总结前言很多时候,后台任务对我们来说是一个利器,帮我们在后面处理了成千上万的事情。在.NET Framework时代,我们可能比较多的就是一个项目,会有一到多个对应的Windows服务,这些Windows服务就可以当作是我们所说的后台任务了。我喜欢将后台任务分为两大类,一类是不停的跑,好比MQ的消费者,RPC的服务端。另一类是定时的跑,好比定时任务。那么在.NET Core时代是不是有一些不同的解决方案呢?答案是肯定的。Generic Host就是其中一种方案,也是本文的主角。什么是Generic HostGeneric Host是ASP.NET Core 2.1中的新增功能,它的目的是将HTTP管道从Web Host的API中分离出来,从而启用更多的Host方案。这样可以让基于Generic Host的一些特性延用一些基础的功能。如:如配置、依赖关系注入和日志等。Generic Host更倾向于通用性,换句话就是说,我们即可以在Web项目中使用,也可以在非Web项目中使用!虽然有时候后台任务混杂在Web项目中并不是一个太好的选择,但也并不失是一个解决方案。尤其是在资源并不充足的时候。比较好的做法还是让其独立出来,让它的职责更加单一。下面就先来看看如何创建后台任务吧。后台任务示例我们先来写两个后台任务(一个一直跑,一个定时跑),体验一下这些后台任务要怎么上手,同样也是我们后面要使用到的。这两个任务统一继承BackgroundService这个抽象类,而不是IHostedService这个接口。后面会说到两者的区别。一直跑的后台任务先上代码public class PrinterHostedService2 : BackgroundService{private readonly ILogger _logger;private readonly AppSettings _settings;public PrinterHostedService2(ILoggerFactory loggerFactory, IOptionsSnapshot<AppSettings> options){this._logger = loggerFactory.CreateLogger<PrinterHostedService2>();this._settings = options.Value;}public override Task StopAsync(CancellationToken cancellationToken){_logger.LogInformation("Printer2 is stopped");return Task.CompletedTask;}protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){_logger.LogInformation($"Printer2 is working. {_settings.PrinterDelaySecond}");await Task.Delay(TimeSpan.FromSeconds(_settings.PrinterDelaySecond), stoppingToken);}}}来看看里面的细节。我们的这个服务继承了BackgroundService,就一定要实现里面的ExecuteAsync,至于StartAsync和StopAsync等方法可以选择性的override。我们ExecuteAsync在里面就是输出了一下日志,然后休眠在配置文件中指定的秒数。这个任务可以说是最简单的例子了,其中还用到了依赖注入,如果想在任务中注入数据仓储之类的,应该就不需要再多说了。同样的方式再写一个定时的。定时跑的后台任务这里借助了Timer来完成定时跑的功能,同样的还可以结合Quartz来完成。public class TimerHostedService : BackgroundService{//other ...private Timer _timer;protected override Task ExecuteAsync(CancellationToken stoppingToken){_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(_settings.TimerPeriod));return Task.CompletedTask;}private void DoWork(object state){_logger.LogInformation("Timer is working");}public override Task StopAsync(CancellationToken cancellationToken){_logger.LogInformation("Timer is stopping");_timer?.Change(Timeout.Infinite, 0);return base.StopAsync(cancellationToken);}public override void Dispose(){_timer?.Dispose();base.Dispose();}}和第一个后台任务相比,没有太大的差异。下面我们先来看看如何用控制台的形式来启动这两个任务。控制台形式这里会同时引入NLog来记录任务跑的日志,方便我们观察。Main函数的代码如下:class Program{static async Task Main(string[] args){var builder = new HostBuilder()//logging.ConfigureLogging(factory =>{//use nlogfactory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true });NLog.LogManager.LoadConfiguration("nlog.config");})//host config.ConfigureHostConfiguration(config =>{//command lineif (args != null){config.AddCommandLine(args);}})//app config.ConfigureAppConfiguration((hostContext, config) =>{var env = hostContext.HostingEnvironment;config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);config.AddEnvironmentVariables();if (args != null){config.AddCommandLine(args);}})//service.ConfigureServices((hostContext, services) =>{services.AddOptions();services.Configure<AppSettings>(hostContext.Configuration.GetSection("AppSettings"));//basic usageservices.AddHostedService<PrinterHostedService2>();services.AddHostedService<TimerHostedService>();}) ;//consoleawait builder.RunConsoleAsync();////start and wait for shutdown//var host = builder.Build();//using (host)//{// await host.StartAsync();// await host.WaitForShutdownAsync();//}}}对于控制台的方式,需要我们对HostBuilder有一定的了解,虽说它和WebHostBuild有相似的地方。可能大部分时候,我们是直接使用了WebHost.CreateDefaultBuilder(args)来构造的,如果对CreateDefaultBuilder里面的内容没有了解,那么对上面的代码可能就不会太清晰。上述代码的大致流程如下:new一个HostBuilder对象配置日志,主要是接入了NLogHost的配置,这里主要是引入了CommandLine,因为需要传递参数给程序应用的配置,指定了配置文件,和引入CommandLineService的配置,这个就和我们在Startup里面写的差不多了,最主要的是我们的后台服务要在这里注入启动其中,2-5的顺序可以按个人习惯来写,里面的内容也和我们写Startup大同小异。第6步,启动的时候,有多种方式,这里列出了两种行为等价的方式。a. 通过RunConsoleAsync的方式来启动b. 先StartAsync然后再WaitForShutdownAsyncRunConsoleAsync的奥秘,我觉得还是直接看下面的代码比较容易懂。/// <summary>/// Listens for Ctrl+C or SIGTERM and calls <see cref="IApplicationLifetime.StopApplication"/> to start the shutdown process./// This will unblock extensions like RunAsync and WaitForShutdownAsync./// </summary>/// <param name="hostBuilder">The <see cref="IHostBuilder" /> to configure.</param>/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>public static IHostBuilder UseConsoleLifetime(this IHostBuilder hostBuilder){return hostBuilder.ConfigureServices((context, collection) => collection.AddSingleton<IHostLifetime, ConsoleLifetime>());}/// <summary>/// Enables console support, builds and starts the host, and waits for Ctrl+C or SIGTE

<div >
<p >目录


<div >

很多时候,后台任务对我们来说是一个利器,帮我们在后面处理了成千上万的事情。

在.NET Framework时代,我们可能比较多的就是一个项目,会有一到多个对应的windows服务,这些windows服务就可以当作是我们所说的后台任务了。

我喜欢将后台任务分为两大类,一类是不停的跑,好比MQ的消费者,RPC的服务端。另一类是定时的跑,好比定时任务。

那么在.NET Core时代是不是有一些不同的解决方案呢?答案是肯定的。

Generic Host就是其中一种方案,也是本文的主角。

Generic Host是ASP.NET Core 2.1中的新增功能,它的目的是将http管道从Web Host的API中分离出来,从而启用更多的Host方案。

这样可以让基于Generic Host的一些特性延用一些基础的功能。如:如配置、依赖关系注入和日志等。

Generic Host更倾向于通用性,换句话就是说,我们即可以在Web项目中使用,也可以在非Web项目中使用!

虽然有时候后台任务混杂在Web项目中并不是一个太好的选择,但也并不失是一个解决方案。尤其是在资源并不充足的时候。

比较好的做法还是让其独立出来,让它的职责更加单一。

下面就先来看看如何创建后台任务吧。

我们先来写两个后台任务(一个一直跑,一个定时跑),体验一下这些后台任务要怎么上手,同样也是我们后面要使用到的。

这两个任务统一继承BackgroundService这个抽象类,而不是IHostedService这个接口。后面会说到两者的区别。

一直跑的后台任务

先上代码

public class PrinterHostedService2 : BackgroundService{    private Readonly ILogger _logger;    private Readonly AppSettings _settings;
pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c PrinterHostedService2(ILoggerFactory loggerFactory,IOptionsSnapshot<AppSettings> options){    this._logger = loggerFactory.CreateLogger<PrinterHostedService2>();    this._settings = options.Value;}public override Task StopAsync(CancellationToken cancellationToken){    _logger.LogInformation("Printer2 is stopped");    return Task.CompletedTask;}protected override async Task ExecuteAsync(CancellationToken stoppingToken){    while (!stoppingToken.IsCancellationRequested)    {        _logger.LogInformation($"Printer2 is working. {_settings.PrinterDelaySecond}");        await Task.Delay(TimeSpan.FromSeconds(_settings.PrinterDelaySecond),stoppingToken);    }}

}

来看看里面的细节。

我们的这个服务继承了BackgroundService,就一定要实现里面的ExecuteAsync,至于StartAsync和StopAsync等方法可以选择性的override。

我们ExecuteAsync在里面就是输出了一下日志,然后休眠在配置文件中指定的秒数。

这个任务可以说是最简单的例子了,其中还用到了依赖注入,如果想在任务中注入数据仓储之类的,应该就不需要再多说了。

同样的方式再写一个定时的。

定时跑的后台任务

这里借助了Timer来完成定时跑的功能,同样的还可以结合Quartz来完成。

public class TimerHostedService : BackgroundService{    //other ...
private Timer _timer;protected override Task ExecuteAsync(CancellationToken stoppingToken){    _timer = new Timer(DoWork,null,TimeSpan.Zero,TimeSpan.FromSeconds(_settings.TimerPeriod));    return Task.CompletedTask;}private void DoWork(object state){    _logger.LogInformation("Timer is working");}public override Task StopAsync(CancellationToken cancellationToken){    _logger.LogInformation("Timer is stopping");    _timer?.Change(Timeout.Infinite,0);    return base.StopAsync(cancellationToken);}public override void Dispose(){    _timer?.Dispose();    base.Dispose();}

}

和第一个后台任务相比,没有太大的差异。

下面我们先来看看如何用控制台的形式来启动这两个任务。

这里会同时引入NLog来记录任务跑的日志,方便我们观察。

Main函数的代码如下:

class Program{    static async Task Main(string[] args)    {        var builder = new HostBuilder()            //logging            .ConfigureLogging(factory =>            {                //use nlog                factory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true,CaptureMessageProperties = true });                NLog.LogManager.LoadConfiguration("nlog.config");            })            //host config            .ConfigureHostConfiguration(config =>            {                //command line                if (args != null)                {                    config.AddCommandLine(args);                }            })            //app config            .ConfigureAppConfiguration((hostContext,config) =>            {                var env = hostContext.HostingEnvironment;                config.AddJsonFile("appsettings.json",optional: true,reloadOnChange: true)                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json",reloadOnChange: true);
            config.AddEnvironmentVariables();            if (args != null)            {                config.AddCommandLine(args);            }        })        //service        .ConfigureServices((hostContext,services) =>        {            services.AddOptions();            services.Configure<AppSettings>(hostContext.Configuration.GetSection("AppSettings"));            //basic usage            services.AddHostedService<PrinterHostedService2>();            services.AddHostedService<TimerHostedService>();        }) ;    //console     await builder.RunConsoleAsync();    ////start and wait for shutdown    //var host = builder.Build();    //using (host)    //{    //    await host.StartAsync();    //    await host.WaitForShutdownAsync();    //}}

}

对于控制台的方式,需要我们对HostBuilder有一定的了解,虽说它和WebHostBuild有相似的地方。可能大部分时候,我们是直接使用了WebHost.CreateDefaultBuilder(args)来构造的,如果对CreateDefaultBuilder里面的内容没有了解,那么对上面的代码可能就不会太清晰。

上述代码的大致流程如下:

new一个HostBuilder对象配置日志,主要是接入了NLogHost的配置,这里主要是引入了CommandLine,因为需要传递参数给程序应用的配置,指定了配置文件,和引入CommandLineService的配置,这个就和我们在Startup里面写的差不多了,最主要的是我们的后台服务要在这里注入启动

其中,

2-5的顺序可以按个人习惯来写,里面的内容也和我们写Startup大同小异。

第6步,启动的时候,有多种方式,这里列出了两种行为等价的方式。

a. 通过RunConsoleAsync的方式来启动

b. 先StartAsync然后再WaitForShutdownAsync

RunConsoleAsync的奥秘,我觉得还是直接看下面的代码比较容易懂。

/// /// Listens for Ctrl+C or SIGTERM and calls ///  to configure./// The same instance of the public static IHostBuilder UseConsoleLifetime(this IHostBuilder hostBuilder){    return hostBuilder.ConfigureServices((context,collection) => collection.AddSingleton());}

///


/// Enables console support,builds and starts the host,and waits for Ctrl+C or SIGTERM to shut down.
///

/// <param name="hostBuilder">The to configure.
/// <param name="cancellationToken">
///
public static Task RunConsoleAsync(this IHostBuilder hostBuilder,CancellationToken cancellationToken = default)
{
return hostBuilder.UseConsoleLifetime().Build().RunAsync(cancellationToken);
}

这里涉及到了一个比较重要的IHostLifetime,Host的生命周期,ConsoleLifeTime是默认的一个,可以理解成当接收到ctrl+c这样的指令时,它就会触发停止。

接下来,写一下nlog的配置文件

                

这个时候已经可以通过命令启动我们的应用了。

dotnet run -- --environment Staging

这里指定了运行环境为Staging,而不是默认的Production。

在构造HostBuilder的时候,可以通过UseEnvironment或ConfigureHostConfiguration直接指定运行环境,但是个人更加倾向于在启动命令中去指定,避免一些不可控因素。

这个时候大致效果如下:

虽然效果已经出来了,不过大家可能会觉得这个有点小打小闹,下面来个略微复杂一点的后台任务,用来监听并消费RabbitMQ的消息。

public class ComsumeRabbitMQHostedService : BackgroundService{    private readonly ILogger _logger;    private readonly AppSettings _settings;    private IConnection _connection;    private IModel _channel;
public ComsumeRabbitMQHostedService(ILoggerFactory loggerFactory,IOptionsSnapshot<AppSettings> options){    this._logger = loggerFactory.CreateLogger<ComsumeRabbitMQHostedService>();    this._settings = options.Value;    InitRabbitMQ(this._settings);}private void InitRabbitMQ(AppSettings settings){    var factory = new ConnectionFactory { HostName = settings.HostName,};    _connection = factory.CreateConnection();    _channel = _connection.CreateModel();    _channel.ExchangeDeclare(_settings.ExchangeName,ExchangeType.Topic);    _channel.QueueDeclare(_settings.QueueName,false,null);    _channel.QueueBind(_settings.QueueName,_settings.ExchangeName,_settings.RoutingKey,null);    _channel.BasicQos(0,1,false);    _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown;}protected override Task ExecuteAsync(CancellationToken stoppingToken){    stoppingToken.ThrowIfCancellationRequested();    var consumer = new EventingBasicConsumer(_channel);    consumer.Received += (ch,ea) =>    {        var content = System.Text.Encoding.UTF8.GetString(ea.Body);        HandleMessage(content);        _channel.BasicAck(ea.DeliveryTag,false);    };    consumer.Shutdown += OnConsumerShutdown;    consumer.Registered += OnConsumerRegistered;    consumer.Unregistered += OnConsumerUnregistered;    consumer.ConsumerCancelled += OnConsumerConsumerCancelled;    _channel.BasicConsume(_settings.QueueName,consumer);    return Task.CompletedTask;}private void HandleMessage(string content){    _logger.LogInformation($"consumer received {content}");}private void OnConsumerConsumerCancelled(object sender,ConsumerEventArgs e)  { ... }private void OnConsumerUnregistered(object sender,ConsumerEventArgs e) { ... }private void OnConsumerRegistered(object sender,ConsumerEventArgs e) { ... }private void OnConsumerShutdown(object sender,ShutdownEventArgs e) { ... }private void RabbitMQ_ConnectionShutdown(object sender,ShutdownEventArgs e)  { ... }public override void Dispose(){    _channel.Close();    _connection.Close();    base.Dispose();}

}

代码细节就不需要多说了,下面就启动MQ发送程序来模拟消息的发送

同时看我们任务的日志输出

由启动到停止,效果都是符合我们预期的。

下面再来看看Web形式的后台任务是怎么处理的。

这种模式下的后台任务,其实就是十分简单的了。

我们只要在Startup的ConfigureServices方法里面注册我们的几个后台任务就可以了。

public void ConfigureServices(IServiceCollection services){    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);    services.AddHostedService();    services.AddHostedService();    services.AddHostedService();}

启动Web站点后,我们发了20条MQ消息,再访问了一下Web站点的首页,最后是停止站点。

下面是日志结果,都是符合我们的预期。

可能大家会比较好奇,这三个后台任务是怎么混合在Web项目里面启动的。

答案就在下面的两个链接里。

href="https://github.com/aspnet/Hosting/blob/2.1.1/src/Microsoft.AspNetCore.Hosting/Internal/HostedServiceExecutor.cs" >https://github.com/aspnet/Hosting/blob/2.1.1/src/Microsoft.AspNetCore.Hosting/Internal/HostedServiceExecutor.cs

上面说了那么多,都是在本地直接运行的,可能大家会比较关注这个要怎样部署,下面我们就不看看怎么部署。

部署的话,针对不同的情形(web和非web)都有不同的选择。

正常来说,如果本身就是web程序,那么平时我们怎么部署的,就和平时那样部署即可。

花点时间讲讲部署非web的情形。

其实这里的部署等价于让程序在后台运行。

在linux下面让程序在后台运行方式有好多好多,Supervisor、Screen、pm2、systemctl等。

这里主要介绍一下systemctl,同时用上面的例子来进行部署,由于个人服务器没有MQ环境,所以没有启用消费MQ的后台任务。

先创建一个 service 文件

vim /etc/systemd/system/ghostdemo.service

内容如下:

[Unit]Description=Generic Host Demo

[Service]
WorkingDirectory=/var/www/ghost
ExecStart=/usr/bin/dotnet /var/www/ghost/ConsoleGHost.dll --environment Staging
KillSignal=SIGINT
SyslogIDentifIEr=ghost-example

[Install]
WantedBy=multi-user.target

其中,各项配置的含义可以自行查找,这里不作说明。

然后可以通过下面的命令来启动和停止这个服务

service ghostdemo startservice ghostdemo stop 

测试无误之后,就可以设为自启动了。

systemctl enable ghostdemo.service

下面来看看运行的效果

我们先启动服务,然后去查看实时日志,可以看到应用的日志不停的输出。

当我们停了服务,再看实时日志,就会发现我们的两个后台任务已经停止了,也没有日志再进来了。

再去看看服务系统日志

sudo journalctl -fu ghostdemo.service

发现它确实也是停了。

在这里,我们还可以看到服务的当前环境和根路径。

前面的所有示例中,我们用的都是BackgroundService,而不是IHostedService。

这两者有什么区别呢?

可以这样简单的理解,IHostedService是原料,BackgroundService是一个用原料加工过一部分的半成品。

这两个都是不能直接当成成品来用的,都需要进行加工才能做成一个可用的成品。

同时也意味着,如果使用IHostedService可能会需要做比较多的控制。

基于前面的打印后台任务,在这里使用IHostedService来实现。

如果我们只是纯綷的把实现代码放到StartAsync方法中,那么可能就会有惊喜了。

public class PrinterHostedService : IHostedService,Idisposable{    //other ....
pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c async Task StartAsync(CancellationToken cancellationToken){    while (!cancellationToken.IsCancellationRequested)    {        Cons<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a>e.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne("Printer is working.");        await Task.Delay(TimeSpan.FromSeconds(_settings.PrinterDelaySecond),cancellationToken);    }}pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c Task S<a href="https://m.jb51.cc/tag/top/" target="_blank" >top</a>Async(CancellationToken cancellationToken){    Cons<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a>e.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne("Printer is s<a href="https://m.jb51.cc/tag/top/" target="_blank" >top</a>ped");    return Task.CompletedTask;}

}

运行之后,想用ctrl+c来停止,发现还是一直在跑。

ps一看,这个进程还在,kill掉之后才不会继续输出。。

问题出在那里呢?原因其实还是比较明显的,因为这个任务还没有启动成功,一直处于启动中的状态!

换句话说,StartAsync方法还没有执行完。这个问题一定要小心再小心。

要怎么处理这个问题呢?解决方法也比较简单,可以通过引用一个变量来记录要运行的任务,将其从StartAsync方法中解放出来。

public class PrinterHostedService3 : IHostedService,Idisposable{    //others .....    private bool _stopPing;    private Task _backgroundTask;
pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c Task StartAsync(CancellationToken cancellationToken){    Cons<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a>e.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne("Printer3 is starting.");    _backgroundTask = BackgroundTask(cancellationToken);    return Task.CompletedTask;}private async Task BackgroundTask(CancellationToken cancellationToken){    while (!_s<a href="https://m.jb51.cc/tag/top/" target="_blank" >top</a><a href="https://m.jb51.cc/tag/Ping/" target="_blank" >Ping</a>)    {        await Task.Delay(TimeSpan.FromSeconds(_settings.PrinterDelaySecond),cancellationToken);        Cons<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a>e.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne("Printer3 is doing background work.");    }}pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c Task S<a href="https://m.jb51.cc/tag/top/" target="_blank" >top</a>Async(CancellationToken cancellationToken){    Cons<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a>e.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne("Printer3 is s<a href="https://m.jb51.cc/tag/top/" target="_blank" >top</a><a href="https://m.jb51.cc/tag/Ping/" target="_blank" >Ping</a>.");    _s<a href="https://m.jb51.cc/tag/top/" target="_blank" >top</a><a href="https://m.jb51.cc/tag/Ping/" target="_blank" >Ping</a> = true;    return Task.CompletedTask;}pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c vo<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a> <a href="https://www.jb51.cc/tag/dis/" target="_blank" >dis</a>pose(){    Cons<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a>e.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne("Printer3 is <a href="https://www.jb51.cc/tag/dis/" target="_blank" >dis</a>posing.");}

}

这样就能让这个任务真正的启动成功了!效果就不放图了。

相对来说,BackgroundService用起来会比较简单,实现核心的ExecuteAsync这个抽象方法就差不多了,出错的概率也会比较低。

在注册服务的时候,我们还可以通过编写IHostBuilder的扩展方法来完成。

public static class Extensions{    public static IHostBuilder UseHostedService(this IHostBuilder hostBuilder)        where T : class,IHostedService,Idisposable    {        return hostBuilder.ConfigureServices(services =>            services.AddHostedService());    }
pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c static IHostBuilder UseComsumeRabbitMQ(this IHostBuilder hostBuilder){    return hostBuilder.Con<a href="https://www.jb51.cc/tag/fig/" target="_blank" >fig</a>ureServices(services =>             services.AddHostedService<ComsumeRabbitMQHostedService>());}

}

使用的时候就可以像下面一样。

var builder = new HostBuilder()        //others ...        .ConfigureServices((hostContext,services) =>        {            services.AddOptions();            services.Configure(hostContext.Configuration.GetSection("AppSettings"));
        //basic usage        //services.AddHostedService<PrinterHostedService2>();        //services.AddHostedService<TimerHostedService>();        //services.AddHostedService<ComsumeRabbitMQHostedService>();    })    //extensions usage    .UseComsumeRabbitMQ()    .UseHostedService<TimerHostedService>()    .UseHostedService<PrinterHostedService2>()    //.UseHostedService<ComsumeRabbitMQHostedService>()    ;</code></pre>

<h2 ID="总结">总结

Generic Host让我们可以用熟悉的方式来处理后台任务,不得不说这是一个很 总结

以上是内存溢出为你收集整理的谈谈.NET Core中基于Generic Host来实现后台任务全部内容,希望文章能够帮你解决谈谈.NET Core中基于Generic Host来实现后台任务所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1256041.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-07
下一篇 2022-06-07

发表评论

登录后才能评论

评论列表(0条)

保存