[2core]IHostedService和Thread+CancellationToken

從.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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章