从.net framework升级到.net core,我还是很开心的,主要是因为“原先很多需要自己构思实现的功能,现在框架已经提供了解决方案”,不用自己折腾了。
本文主要记录IHostedService接口的使用过程,以及通过CancellationToken控制Thread和Task的中止状态。
一、需求
在asp.net webapi项目里,都会开启几个线程用来处理日志、消息、心跳等,由于这些功能与项目的主要功能没有直接关系,所以也有很多团队并没有这块功能,但是我们很关心这方面的东西,因为有了它们运维就会更轻松。现在它们又被称之为SideCar模式,就是抗日电视剧中常见的带兜的摩托车,本管它叫什么名称,只要是能实现需要的功能即可。从asp.net升级到asp.net core,自然是想研究一下“框架是否提供这方面的功能”,后来发现确实提供了SideCar模式的解决方案,即通过IHostedService接口实现,它提供了Start和Stop两个需要实现的接口方法,这是让以“系统服务”的方式实现呀!是的。
二、设计
对IHostedService了解过后,发现它还是比较简单的,程序设计如下:
1.Program类完成服务注册
2.TestService类实现IHostedService接口
3.在TestService.Start方法中,调用TestWorker和TestTask的Start方法,从而启动服务,服务内部实现:每秒钟输出一条信息。
4.TestWorker类使用Thread实现
5.TestTask类使用Task.Run实现
三、实现
1.创建TestService类
public class TestService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("TestService.StartAsync");
TestWorker.Instance.Start();
TestTask.Start();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
TestTask.Stop();
TestWorker.Instance.Stop();
Console.WriteLine("TestService.StopAsync");
return Task.CompletedTask;
}
}
2.创建TestWorker类
public class TestWorker
{
private static object _objLocker = new object();
private static TestWorker _instance;
public static TestWorker Instance
{
get
{
if (_instance == null)
{
lock (_objLocker)
{
if (_instance == null)
{
_instance = new TestWorker();
}
}
}
return _instance;
}
}
private TestWorker() { }
private Thread _worker;
private CancellationTokenSource _workerCTS;
public void Start()
{
_worker = new Thread(ExecWork);
_worker.Start();
_workerCTS = new CancellationTokenSource();
_workerCTS.Token.Register(() => Console.WriteLine($"线程{_worker.ManagedThreadId}被取消了."));
}
public void Stop()
{
if (_worker != null)
{
if (_workerCTS != null)
{
_workerCTS.Cancel();
}
_worker = null;
}
}
private void ExecWork(object obj)
{
while (!_workerCTS.IsCancellationRequested)
{
//执行任务
TestMethod();
//休息1000毫秒
Thread.Sleep(1000);
}
}
private void TestMethod()
{
var nowTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Console.WriteLine($"现在时间(线程):{nowTime}");
}
}
3.创建TestTask类
public class TestTask
{
private static CancellationTokenSource _taskCTS;
public static void Start()
{
_taskCTS = new CancellationTokenSource();
_taskCTS.Token.Register(() => Console.WriteLine($"任务被取消了."));
Task.Run(() =>
{
while (!_taskCTS.IsCancellationRequested)
{
var nowTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
Console.WriteLine($"现在时间(任务):{nowTime}");
Thread.Sleep(1000);
}
});
}
public static void Stop()
{
if (_taskCTS != null)
{
_taskCTS.Cancel();
}
}
}
4.注入TestService
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddSingleton<IHostedService, TestService>();
//builder.Services.AddHostedService<TestService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapControllers();
app.Run();
5.测试运行,输出结果
TestService.StartAsync
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5298
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: F:\Codes\.net6\tests\jks.core.test.hostedservice\jks.core.test.hostedservice\
现在时间(任务):2022-12-22 13:55:19
现在时间(线程):2022-12-22 13:55:19
现在时间(线程):2022-12-22 13:55:20
现在时间(任务):2022-12-22 13:55:20
现在时间(任务):2022-12-22 13:55:21
现在时间(线程):2022-12-22 13:55:21
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
任务被取消了.
线程12被取消了.
TestService.StopAsync
四、CancellationToken
在上述过程中,我觉得CancellationToken是个知识点。之前在使用Thread的时候,总是通过Thread.Abort方法终止线程,由于线程没有跟非托管数据交互,所以知道不会存在问题。但是到了.net core时,发现不好使用了,已被框架标识为放弃,且建议使用CancellationToken终止线程,既然如此就研究一下呗。
1)要知道CancellationToken是由CancellationTokenSource产生的,所以要看看CancellationTokenSource
a.它有三个构造函数:
CancellationTokenSource():初始化 CancellationTokenSource 类的新实例。
CancellationTokenSource(Int32):初始化 CancellationTokenSource 类的新实例,在指定的延迟(以毫秒为单位)后将被取消。
CancellationTokenSource(TimeSpan):初始化 CancellationTokenSource 类的新实例,在指定的时间跨度后将被取消。
b.两个属性:
IsCancellationRequested:获取是否已请求取消此 CancellationTokenSource。
Token:获取与此 CancellationToken 关联的 CancellationTokenSource。
c.三个常用方法:
Cancel():传达取消请求。直接取消。
CancelAfter(Int32):在指定的毫秒数后计划对此 CancellationTokenSource 的取消操作。定时取消。
CreateLinkedTokenSource(CancellationToken[]):创建一个将在指定的数组中任何源标记处于取消状态时处于取消状态的 CancellationTokenSource。关联取消。
2)了解完CancellationTokenSource后,现在再看CancellationToken。它有一个常用的Register方法,作用是“注册一个将在取消此 CancellationToken 时调用的委托。”
3)现在通过示例演示一下上述使用方法
a.直接取消
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = cancellationTokenSource.Token;
cancellationToken.Register(() => Console.WriteLine("已取消。"));
Console.WriteLine("马上要取消了哦!");
cancellationTokenSource.Cancel();
b.定时取消
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Token.Register(() => System.Console.WriteLine("已取消."));
//五秒之后取消
cancellationTokenSource.CancelAfter(5000);
System.Console.WriteLine("五秒后取消哦!");
c.关联取消
CancellationTokenSource cts1 = new CancellationTokenSource ();
CancellationTokenSource cts2 = new CancellationTokenSource ();
CancellationTokenSource cts3 = new CancellationTokenSource ();
cts1.Token.Register(() => System.Console.WriteLine("cts1被取消了"));
cts2.Token.Register(() => System.Console.WriteLine("cts2被取消了"));
//创建一个关联的CancellationTokenSource
var ctsLink = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token, cts3.Token);
ctsLink.Token.Register(() => System.Console.WriteLine("ctsLink被取消了"));
cts3.Cancel();
4)如果想要了解更多底层实现,请看源码,本文不分析源码。
五、总结
申明一点,本人写的文章,仅仅是为了将十几年积累的技术平台升级到.net core,不教人技术,也自知没那个能力,如果你想系统性的学习,请移步官网,或看大佬们的教学博客与视频。
源码地址:https://gitee.com/kinbor/jks.core.test.hostedservice