真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么在.NETCore中利用基于GenericHost實現(xiàn)后臺任務(wù)-創(chuàng)新互聯(lián)

這篇文章將為大家詳細(xì)講解有關(guān)怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù),文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

目前成都創(chuàng)新互聯(lián)已為上千的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)站空間網(wǎng)站托管、企業(yè)網(wǎng)站設(shè)計、五家渠網(wǎng)站維護(hù)等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

什么是Generic Host

Generic Host是ASP.NET Core 2.1中的新增功能,它的目的是將HTTP管道從Web Host的API中分離出來,從而啟用更多的Host方案。

現(xiàn)在2.1版本的Asp.Net Core中,有了兩種可用的Host。

Web Host –適用于托管Web程序的Host,就是我們所熟悉的在Asp.Net Core應(yīng)用程序的Mai函數(shù)中用CreateWebHostBuilder創(chuàng)建出來的常用的WebHost。


怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

Generic Host (ASP.NET Core 2.1版本才有) – 適用于托管非 Web 應(yīng)用(例如,運(yùn)行后臺任務(wù)的應(yīng)用)。 在未來的版本中,通用主機(jī)將適用于托管任何類型的應(yīng)用,包括 Web 應(yīng)用。 通用主機(jī)最終將取代 Web 主機(jī),這大概也是這種類型的主機(jī)叫做通用主機(jī)的原因。

這樣可以讓基于Generic Host的一些特性延用一些基礎(chǔ)的功能。如:如配置、依賴關(guān)系注入和日志等。

Generic Host更傾向于通用性,換句話就是說,我們即可以在Web項目中使用,也可以在非Web項目中使用!

雖然有時候后臺任務(wù)混雜在Web項目中并不是一個太好的選擇,但也并不失是一個解決方案。尤其是在資源并不充足的時候。

比較好的做法還是讓其獨(dú)立出來,讓它的職責(zé)更加單一。

下面就先來看看如何創(chuàng)建后臺任務(wù)吧。

后臺任務(wù)示例

我們先來寫兩個后臺任務(wù)(一個一直跑,一個定時跑),體驗一下這些后臺任務(wù)要怎么上手,同樣也是我們后面要使用到的。

這兩個任務(wù)統(tǒng)一繼承BackgroundService這個抽象類,而不是IHostedService這個接口。后面會說到兩者的區(qū)別。

1、一直跑的后臺任務(wù)

先上代碼

public class PrinterHostedService2 : BackgroundService
{
 private readonly ILogger _logger;
 private readonly AppSettings _settings;

 public PrinterHostedService2(ILoggerFactory loggerFactory, IOptionsSnapshot options)
 {
 this._logger = loggerFactory.CreateLogger();
 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);
 }
 }
}

來看看里面的細(xì)節(jié)。

我們的這個服務(wù)繼承了BackgroundService,就一定要實現(xiàn)里面的ExecuteAsync,至于StartAsync和StopAsync等方法可以選擇性的override。

我們ExecuteAsync在里面就是輸出了一下日志,然后休眠在配置文件中指定的秒數(shù)。

這個任務(wù)可以說是最簡單的例子了,其中還用到了依賴注入,如果想在任務(wù)中注入數(shù)據(jù)倉儲之類的,應(yīng)該就不需要再多說了。

同樣的方式再寫一個定時的。

定時跑的后臺任務(wù)

這里借助了Timer來完成定時跑的功能,同樣的還可以結(jié)合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();
 }
}

和第一個后臺任務(wù)相比,沒有太大的差異。

下面我們先來看看如何用控制臺的形式來啟動這兩個任務(wù)。

控制臺形式

這里會同時引入NLog來記錄任務(wù)跑的日志,方便我們觀察。

Main函數(shù)的代碼如下:

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", optional: true, reloadOnChange: true);

  config.AddEnvironmentVariables();

  if (args != null)
  {
   config.AddCommandLine(args);
  }
  })
  //service
  .ConfigureServices((hostContext, services) =>
  {
  services.AddOptions();
  services.Configure(hostContext.Configuration.GetSection("AppSettings"));

  //basic usage
  services.AddHostedService();
  services.AddHostedService();
  }) ;

 //console 
 await builder.RunConsoleAsync();

 ////start and wait for shutdown
 //var host = builder.Build();
 //using (host)
 //{
 // await host.StartAsync();

 // await host.WaitForShutdownAsync();
 //}
 }
}

對于控制臺的方式,需要我們對HostBuilder有一定的了解,雖說它和WebHostBuild有相似的地方??赡艽蟛糠謺r候,我們是直接使用了WebHost.CreateDefaultBuilder(args)來構(gòu)造的,如果對CreateDefaultBuilder里面的內(nèi)容沒有了解,那么對上面的代碼可能就不會太清晰。

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

  • new一個HostBuilder對象

  • 配置日志,主要是接入了NLog

  • Host的配置,這里主要是引入了CommandLine,因為需要傳遞參數(shù)給程序

  • 應(yīng)用的配置,指定了配置文件,和引入CommandLine

  • Service的配置,這個就和我們在Startup里面寫的差不多了,最主要的是我們的后臺服務(wù)要在這里注入

  • 啟動

其中,

2-5的順序可以按個人習(xí)慣來寫,里面的內(nèi)容也和我們寫Startup大同小異。

第6步,啟動的時候,有多種方式,這里列出了兩種行為等價的方式。

a. 通過RunConsoleAsync的方式來啟動

b. 先StartAsync然后再WaitForShutdownAsync

RunConsoleAsync的奧秘,我覺得還是直接看下面的代碼比較容易懂。

/// 
/// Listens for Ctrl+C or SIGTERM and calls  to start the shutdown process.
/// This will unblock extensions like RunAsync and WaitForShutdownAsync.
/// 
/// The  to configure.
/// The same instance of the  for chaining.
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.
/// 
/// The  to configure.
/// 
/// 
public static Task RunConsoleAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{
 return hostBuilder.UseConsoleLifetime().Build().RunAsync(cancellationToken);
}

這里涉及到了一個比較重要的IHostLifetime,Host的生命周期,ConsoleLifeTime是默認(rèn)的一個,可以理解成當(dāng)接收到ctrl+c這樣的指令時,它就會觸發(fā)停止。

接下來,寫一下nlog的配置文件




 
 
 

 
 
 
 

這個時候已經(jīng)可以通過命令啟動我們的應(yīng)用了。

dotnet run -- --environment Staging

這里指定了運(yùn)行環(huán)境為Staging,而不是默認(rèn)的Production。

在構(gòu)造HostBuilder的時候,可以通過UseEnvironment或ConfigureHostConfiguration直接指定運(yùn)行環(huán)境,但是個人更加傾向于在啟動命令中去指定,避免一些不可控因素。

這個時候大致效果如下:

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

雖然效果已經(jīng)出來了,不過大家可能會覺得這個有點(diǎn)小打小鬧,下面來個略微復(fù)雜一點(diǎn)的后臺任務(wù),用來監(jiān)聽并消費(fèi)RabbitMQ的消息。

消費(fèi)MQ消息的后臺任務(wù)

public class ComsumeRabbitMQHostedService : BackgroundService
{
 private readonly ILogger _logger;
 private readonly AppSettings _settings;
 private IConnection _connection;
 private IModel _channel;

 public ComsumeRabbitMQHostedService(ILoggerFactory loggerFactory, IOptionsSnapshot options)
 {
 this._logger = loggerFactory.CreateLogger();
 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, false, 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, false, 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();
 }
}

代碼細(xì)節(jié)就不需要多說了,下面就啟動MQ發(fā)送程序來模擬消息的發(fā)送

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

同時看我們?nèi)蝿?wù)的日志輸出

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

由啟動到停止,效果都是符合我們預(yù)期的。

下面再來看看Web形式的后臺任務(wù)是怎么處理的。

Web形式

這種模式下的后臺任務(wù),其實就是十分簡單的了。

我們只要在Startup的ConfigureServices方法里面注冊我們的幾個后臺任務(wù)就可以了。

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

啟動Web站點(diǎn)后,我們發(fā)了20條MQ消息,再訪問了一下Web站點(diǎn)的首頁,最后是停止站點(diǎn)。

下面是日志結(jié)果,都是符合我們的預(yù)期。

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

可能大家會比較好奇,這三個后臺任務(wù)是怎么混合在Web項目里面啟動的。

答案就在下面的兩個鏈接里。

https://github.com/aspnet/Hosting/blob/2.1.1/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs

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

上面說了那么多,都是在本地直接運(yùn)行的,可能大家會比較關(guān)注這個要怎樣部署,下面我們就不看看怎么部署。

部署

部署的話,針對不同的情形(web和非web)都有不同的選擇。

正常來說,如果本身就是web程序,那么平時我們怎么部署的,就和平時那樣部署即可。

花點(diǎn)時間講講部署非web的情形。

其實這里的部署等價于讓程序在后臺運(yùn)行。

在Linux下面讓程序在后臺運(yùn)行方式有好多好多,Supervisor、Screen、pm2、systemctl等。

這里主要介紹一下systemctl,同時用上面的例子來進(jìn)行部署,由于個人服務(wù)器沒有MQ環(huán)境,所以沒有啟用消費(fèi)MQ的后臺任務(wù)。

先創(chuàng)建一個 service 文件

vim /etc/systemd/system/ghostdemo.service

內(nèi)容如下:

[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

其中,各項配置的含義可以自行查找,這里不作說明。

然后可以通過下面的命令來啟動和停止這個服務(wù)

service ghostdemo start
service ghostdemo stop

測試無誤之后,就可以設(shè)為自啟動了。

systemctl enable ghostdemo.service

下面來看看運(yùn)行的效果

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

我們先啟動服務(wù),然后去查看實時日志,可以看到應(yīng)用的日志不停的輸出。

當(dāng)我們停了服務(wù),再看實時日志,就會發(fā)現(xiàn)我們的兩個后臺任務(wù)已經(jīng)停止了,也沒有日志再進(jìn)來了。

再去看看服務(wù)系統(tǒng)日志

sudo journalctl -fu ghostdemo.service

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

發(fā)現(xiàn)它確實也是停了。

在這里,我們還可以看到服務(wù)的當(dāng)前環(huán)境和根路徑。

IHostedService和BackgroundService的區(qū)別

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

這兩者有什么區(qū)別呢?

可以這樣簡單的理解,IHostedService是原料,BackgroundService是一個用原料加工過一部分的半成品。

這兩個都是不能直接當(dāng)成成品來用的,都需要進(jìn)行加工才能做成一個可用的成品。

同時也意味著,如果使用IHostedService可能會需要做比較多的控制。

基于前面的打印后臺任務(wù),在這里使用IHostedService來實現(xiàn)。

如果我們只是純綷的把實現(xiàn)代碼放到StartAsync方法中,那么可能就會有驚喜了。

public class PrinterHostedService : IHostedService, IDisposable
{
 //other ....
 
 public async Task StartAsync(CancellationToken cancellationToken)
 {
  while (!cancellationToken.IsCancellationRequested)
  {
   Console.WriteLine("Printer is working.");
   await Task.Delay(TimeSpan.FromSeconds(_settings.PrinterDelaySecond), cancellationToken);
  }
 }

 public Task StopAsync(CancellationToken cancellationToken)
 {
  Console.WriteLine("Printer is stopped");
  return Task.CompletedTask;
 }
}

運(yùn)行之后,想用ctrl+c來停止,發(fā)現(xiàn)還是一直在跑。

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

ps一看,這個進(jìn)程還在,kill掉之后才不會繼續(xù)輸出。。

怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)

問題出在那里呢?原因其實還是比較明顯的,因為這個任務(wù)還沒有啟動成功,一直處于啟動中的狀態(tài)!

換句話說,StartAsync方法還沒有執(zhí)行完。這個問題一定要小心再小心。

要怎么處理這個問題呢?解決方法也比較簡單,可以通過引用一個變量來記錄要運(yùn)行的任務(wù),將其從StartAsync方法中解放出來。

public class PrinterHostedService3 : IHostedService, IDisposable
{
 //others .....
 private bool _stopping;
 private Task _backgroundTask;

 public Task StartAsync(CancellationToken cancellationToken)
 {
  Console.WriteLine("Printer3 is starting.");
  _backgroundTask = BackgroundTask(cancellationToken);
  return Task.CompletedTask;
 }

 private async Task BackgroundTask(CancellationToken cancellationToken)
 {
  while (!_stopping)
  {
   await Task.Delay(TimeSpan.FromSeconds(_settings.PrinterDelaySecond),cancellationToken);
   Console.WriteLine("Printer3 is doing background work.");
  }
 }

 public Task StopAsync(CancellationToken cancellationToken)
 {
  Console.WriteLine("Printer3 is stopping.");
  _stopping = true;
  return Task.CompletedTask;
 }

 public void Dispose()
 {
  Console.WriteLine("Printer3 is disposing.");
 }
}

這樣就能讓這個任務(wù)真正的啟動成功了!效果就不放圖了。

相對來說,BackgroundService用起來會比較簡單,實現(xiàn)核心的ExecuteAsync這個抽象方法就差不多了,出錯的概率也會比較低。

IHostBuilder的擴(kuò)展寫法

在注冊服務(wù)的時候,我們還可以通過編寫IHostBuilder的擴(kuò)展方法來完成。

public static class Extensions
{
 public static IHostBuilder UseHostedService(this IHostBuilder hostBuilder)
  where T : class, IHostedService, IDisposable
 {
  return hostBuilder.ConfigureServices(services =>
   services.AddHostedService());
 }

 public static IHostBuilder UseComsumeRabbitMQ(this IHostBuilder hostBuilder)
 {
  return hostBuilder.ConfigureServices(services =>
     services.AddHostedService());
 }
}

使用的時候就可以像下面一樣。

var builder = new HostBuilder()
  //others ...
  .ConfigureServices((hostContext, services) =>
  {
   services.AddOptions();
   services.Configure(hostContext.Configuration.GetSection("AppSettings"));

   //basic usage
   //services.AddHostedService();
   //services.AddHostedService();
   //services.AddHostedService();
  })
  //extensions usage
  .UseComsumeRabbitMQ()
  .UseHostedService()
  .UseHostedService()
  //.UseHostedService()
  ;

關(guān)于怎么在.NET Core中利用基于Generic Host實現(xiàn)后臺任務(wù)就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。


網(wǎng)站名稱:怎么在.NETCore中利用基于GenericHost實現(xiàn)后臺任務(wù)-創(chuàng)新互聯(lián)
文章網(wǎng)址:http://weahome.cn/article/cssdgc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部