這是一文說通系列的第二篇,裡面有些內容會用到第一篇中間件的部分概念。如果需要,可以參看第一篇:一文說通Dotnet Core的中間件 一、前言 後臺任務在一些特殊的應用場合,有相當的需求。 比方,我們需要實現一個定時任務、或周期性的任務、或非API輸出的業務響應、或不允許併發的業務處理,像提現、支付 ...
這是一文說通系列的第二篇,裡面有些內容會用到第一篇中間件的部分概念。如果需要,可以參看第一篇:一文說通Dotnet Core的中間件
一、前言
後臺任務在一些特殊的應用場合,有相當的需求。
比方,我們需要實現一個定時任務、或周期性的任務、或非API輸出的業務響應、或不允許併發的業務處理,像提現、支付回調等,都需要用到後臺任務。
通常,我們在實現後臺任務時,有兩種選擇:WebAPI和Console。
下麵,我們會用實際的代碼,來理清這兩種工程模式下,後臺任務的開發方式。
為了防止不提供原網址的轉載,特在這裡加上原文鏈接:https://www.cnblogs.com/tiger-wang/p/13081020.html
二、開發環境&基礎工程
這個Demo的開發環境是:Mac + VS Code + Dotnet Core 3.1.2。
$ dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 3.1.201
Commit: b1768b4ae7
Runtime Environment:
OS Name: Mac OS X
OS Version: 10.15
OS Platform: Darwin
RID: osx.10.15-x64
Base Path: /usr/local/share/dotnet/sdk/3.1.201/
Host (useful for support):
Version: 3.1.3
Commit: 4a9f85e9f8
.NET Core SDKs installed:
3.1.201 [/usr/local/share/dotnet/sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
首先,在這個環境下建立工程:
- 創建Solution
% dotnet new sln -o demo
The template "Solution File" was created successfully.
- 這次,我們用Webapi創建工程
% cd demo
% dotnet new webapi -o webapidemo
The template "ASP.NET Core Web API" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on webapidemo/webapidemo.csproj...
Restore completed in 179.13 ms for demo/demo.csproj.
Restore succeeded.
% dotnet new console -o consoledemo
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on consoledemo/consoledemo.csproj...
Determining projects to restore...
Restored consoledemo/consoledemo.csproj (in 143 ms).
Restore succeeded.
- 把工程加到Solution中
% dotnet sln add webapidemo/webapidemo.csproj
% dotnet sln add consoledemo/consoledemo.csproj
基礎工程搭建完成。
三、在WebAPI下實現一個後臺任務
WebAPI下後臺任務需要作為托管服務來實現,而托管服務,需要實現IHostedService
介面。
首先,我們需要引入一個庫:
% cd webapidemo
% dotnet add package Microsoft.Extensions.Hosting
引入後,我們就有了IHostedService
。
下麵,我們來做一個IHostedService
的派生托管類:
namespace webapidemo
{
public class DemoService : IHostedService
{
public DemoService()
{
}
public Task StartAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}
IHostedService
需要實現兩個方法:StartAsync
和StopAsync
。其中:
StartAsync: 用於啟動後臺任務;
StopAsync:主機Host正常關閉時觸發。
如果派生類中有任何非托管資源,那還可以引入IDisposable
,並通過實現Dispose
來清理非托管資源。
這個類生成後,我們將這個類註入到ConfigureServices
中,以使這個類在Startup.Configure
調用之前被調用:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHostedService<DemoService>();
}
下麵,我們用一個定時器的後臺任務,來加深理解:
namespace webapidemo
{
public class TimerService : IHostedService, IDisposable
{
/* 下麵這兩個參數是演示需要,非必須 */
private readonly ILogger _logger;
private int executionCount = 0;
/* 這個是定時器 */
private Timer _timer;
public TimerService(ILogger<TimerService> logger)
{
_logger = logger;
}
public void Dispose()
{
_timer?.Dispose();
}
private void DoWork(object state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation($"Service proccessing {count}");
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service starting");
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
}
註入到ConfigureServices
中:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHostedService<TimerService>();
}
就OK了。代碼比較簡單,就不解釋了。
四、WebAPI後臺任務的依賴註入變形
上一節的示例,是一個簡單的形態。
下麵,我們按照標準的依賴註入,實現一下這個定時器。
依賴註入的簡單樣式,請參見一文說通Dotnet Core的中間件。
首先,我們創建一個介面IWorkService
:
namespace webapidemo
{
public interface IWorkService
{
Task DoWork();
}
}
再根據IWorkService
,建立一個實體類:
namespace webapidemo
{
public class WorkService : IWorkService
{
private readonly ILogger _logger;
private Timer _timer;
private int executionCount = 0;
public WorkService(ILogger<WorkService> logger)
{
_logger = logger;
}
public async Task DoWork()
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation($"Service proccessing {count}");
}
}
}
這樣就建好了依賴的全部內容。
下麵,創建托管類:
namespace webapidemo
{
public class HostedService : IHostedService, IDisposable
{
private readonly ILogger<HostedService> _logger;
public IServiceProvider Services { get; }
private Timer _timer;
public HostedService(IServiceProvider services, ILogger<HostedService> logger)
{
Services = services;
_logger = logger;
}
public void Dispose()
{
_timer?.Dispose();
}
private void DoWork(object state)
{
_logger.LogInformation("Service working");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IWorkService>();
scopedProcessingService.DoWork().GetAwaiter().GetResult();
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service starting");
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Service stopping");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
}
把托管類註入到ConfigureServices
中:
public