背景
在做离线数据处理时,需要处理的数据量比较大,逻辑很复杂,需要的资源比较多,所以无法立即得到结果,并且客户端也不需要立即得到结果。这种处理任务可以称为 后台任务 或者 “长任务”。
在 .NetCore 2.0 以前,我们是无法通过Web 服务器来处理后台任务的,要么是控制台程序,要么是windows 后台服务; 但是这种类型的应用互操作性比较差,无法很方便的通过http请求来发送指令,执行操作(当然也可以利用WCF实现,但是会陷入网络通信开发的深坑)。
在 .NetCore 2.0 以后的版本中,微软提供了IHostedService API ,可以用于开发在WebServer Host 上执行的后台任务,并且可以非常方便的和AspNetCore 框架融合,充分利用Web 请求的互操作性。
一个 “长任务服务”的设计思路
- 一个“长任务” 分为执行过程和调度过程
- 执行过程就是任务的处理逻辑,而调度过程就是启动任务,将任务放到后台队列,当主机有空再处理。
- 需要知道任务开始、完成情况.
实现代码
1. 队列操作接口
public interface IBackgroundQueue
{
void QueueTask(Func<CancellationToken, Task> task);
Task<Func<CancellationToken, Task>> PopQueue(CancellationToken cancellationToken);
}
2. BackgroundQueue实现
public class BackgroundQueue : IBackgroundQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> Tasks;
private SemaphoreSlim signal;
public BackgroundQueue()
{
Tasks = new ConcurrentQueue<Func<CancellationToken, Task>>();
signal = new SemaphoreSlim(0);
}
public async Task<Func<CancellationToken, Task>> PopQueue(CancellationToken cancellationToken)
{
await signal.WaitAsync(cancellationToken);
Tasks.TryDequeue(out var task);
return task;
}
public void QueueTask(Func<CancellationToken, Task> task)
{
Tasks.Enqueue(task);
signal.Release();
}
}
3、使用BackgroundService 类 作为HostedService 的实现
public class QueueService : BackgroundService
{
private IBackgroundQueue _queue;
public QueueService(IBackgroundQueue queue)
{
_queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (stoppingToken.IsCancellationRequested == false)
{
var task = await _queue.PopQueue(stoppingToken);
await task(stoppingToken);
}
}
}
4. 客户端调用
public async Task<string> ImportAccountObjectByTimeScale([FromBody] TimeScaleRequest request)
{
_queue.QueueTask(async token =>
{
// 将 任务添加到后台队列.
await IDataPipeline.Invoke();
});
//throw new System.NotImplementedException("");
}