日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

一文说通Dotnet Core的后台任务

發(fā)布時(shí)間:2023/12/4 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文说通Dotnet Core的后台任务 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

這是一文說(shuō)通系列的第二篇,里面有些內(nèi)容會(huì)用到第一篇中間件的部分概念。如果需要,可以參看第一篇:一文說(shuō)通Dotnet Core的中間件

?

一、前言

后臺(tái)任務(wù)在一些特殊的應(yīng)用場(chǎng)合,有相當(dāng)?shù)男枨蟆?/p>

比方,我們需要實(shí)現(xiàn)一個(gè)定時(shí)任務(wù)、或周期性的任務(wù)、或非API輸出的業(yè)務(wù)響應(yīng)、或不允許并發(fā)的業(yè)務(wù)處理,像提現(xiàn)、支付回調(diào)等,都需要用到后臺(tái)任務(wù)。

?

通常,我們?cè)趯?shí)現(xiàn)后臺(tái)任務(wù)時(shí),有兩種選擇:WebAPI和Console。

下面,我們會(huì)用實(shí)際的代碼,來(lái)理清這兩種工程模式下,后臺(tái)任務(wù)的開(kāi)發(fā)方式。

二、開(kāi)發(fā)環(huán)境&基礎(chǔ)工程

這個(gè)Demo的開(kāi)發(fā)環(huán)境是:Mac + VS Code + Dotnet Core 3.1.2。

$?dotnet?--info .NET?Core?SDK?(reflecting?any?global.json):Version:???3.1.201Commit:????b1768b4ae7Runtime?Environment:OS?Name:?????Mac?OS?XOS?Version:??10.15OS?Platform:?DarwinRID:?????????osx.10.15-x64Base?Path:???/usr/local/share/dotnet/sdk/3.1.201/Host?(useful?for?support):Version:?3.1.3Commit:??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]

?

首先,在這個(gè)環(huán)境下建立工程:

  • 創(chuàng)建Solution

  • %?dotnet?new?sln?-o?demo The?template?"Solution?File"?was?created?successfully.
  • 這次,我們用Webapi創(chuàng)建工程

  • %?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

    基礎(chǔ)工程搭建完成。

    三、在WebAPI下實(shí)現(xiàn)一個(gè)后臺(tái)任務(wù)

    WebAPI下后臺(tái)任務(wù)需要作為托管服務(wù)來(lái)實(shí)現(xiàn),而托管服務(wù),需要實(shí)現(xiàn)IHostedService接口。

    ?

    首先,我們需要引入一個(gè)庫(kù):

    %?cd?webapidemo %?dotnet?add?package?Microsoft.Extensions.Hosting

    引入后,我們就有了IHostedService。

    ?

    下面,我們來(lái)做一個(gè)IHostedService的派生托管類(lèi):

    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需要實(shí)現(xiàn)兩個(gè)方法:StartAsync和StopAsync。其中:

    StartAsync:用于啟動(dòng)后臺(tái)任務(wù);

    StopAsync:主機(jī)Host正常關(guān)閉時(shí)觸發(fā)。

    ?

    如果派生類(lèi)中有任何非托管資源,那還可以引入IDisposable,并通過(guò)實(shí)現(xiàn)Dispose來(lái)清理非托管資源。

    ?

    這個(gè)類(lèi)生成后,我們將這個(gè)類(lèi)注入到ConfigureServices中,以使這個(gè)類(lèi)在Startup.Configure調(diào)用之前被調(diào)用:

    public?void?ConfigureServices(IServiceCollection?services) {services.AddControllers();services.AddHostedService<DemoService>(); }

    下面,我們用一個(gè)定時(shí)器的后臺(tái)任務(wù),來(lái)加深理解:

    namespace?webapidemo {public?class?TimerService?:?IHostedService,?IDisposable{/*?下面這兩個(gè)參數(shù)是演示需要,非必須?*/private?readonly?ILogger?_logger;private?int?executionCount?=?0;/*?這個(gè)是定時(shí)器?*/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了。代碼比較簡(jiǎn)單,就不解釋了。

    四、WebAPI后臺(tái)任務(wù)的依賴(lài)注入變形

    上一節(jié)的示例,是一個(gè)簡(jiǎn)單的形態(tài)。

    下面,我們按照標(biāo)準(zhǔn)的依賴(lài)注入,實(shí)現(xiàn)一下這個(gè)定時(shí)器。

    ?

    依賴(lài)注入的簡(jiǎn)單樣式,請(qǐng)參見(jiàn)一文說(shuō)通Dotnet Core的中間件。

    ?

    首先,我們創(chuàng)建一個(gè)接口IWorkService:

    namespace?webapidemo {public?interface?IWorkService{Task?DoWork();} }

    再根據(jù)IWorkService,建立一個(gè)實(shí)體類(lèi):

    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}");}} }

    這樣就建好了依賴(lài)的全部?jī)?nèi)容。

    ?

    下面,創(chuàng)建托管類(lèi):

    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;}} }

    把托管類(lèi)注入到ConfigureServices中:

    public?void?ConfigureServices(IServiceCollection?services) {services.AddControllers();services.AddHostedService<HostedService>();services.AddSingleton<IWorkService,?WorkService>(); }

    這樣就完成了。

    ?

    這種模式下,可以根據(jù)注入的內(nèi)容切換應(yīng)用的執(zhí)行內(nèi)容。不過(guò),這種模式需要注意services.AddSingleton、services.AddScoped和services.AddTransient的區(qū)別。

    五、Console下的后臺(tái)任務(wù)

    Console應(yīng)用本身就是后臺(tái)運(yùn)行,所以區(qū)別于WebAPI,它不需要托管運(yùn)行,也不需要Microsoft.Extensions.Hosting庫(kù)。

    我們要做的,就是讓程序運(yùn)行,就OK。

    ?

    下面是一個(gè)簡(jiǎn)單的Console模板:

    namespace?consoledemo {class?Program{private?static?AutoResetEvent?_exitEvent;static?async?Task?Main(string[]?args){/*?確保程序只有一個(gè)實(shí)例在運(yùn)行?*/bool?isRuned;Mutex?mutex?=?new?Mutex(true,?"OnlyRunOneInstance",?out?isRuned);if?(!isRuned)return;await?DoWork();/*?后臺(tái)等待?*/_exitEvent?=?new?AutoResetEvent(false);_exitEvent.WaitOne();}private?static?async?Task?DoWork(){throw?new?NotImplementedException();}} }

    這個(gè)模板有兩個(gè)關(guān)鍵的內(nèi)容:

  • 單實(shí)例運(yùn)行:通常后臺(tái)任務(wù),只需要有一個(gè)實(shí)例運(yùn)行。所以,第一個(gè)小段,是解決單實(shí)例運(yùn)行的。多次啟動(dòng)時(shí),除了第一個(gè)實(shí)例外,其它的實(shí)例會(huì)自動(dòng)退出;

  • 后臺(tái)等待:看過(guò)很多人寫(xiě)的,在這兒做后臺(tái)等待時(shí),用了一個(gè)無(wú)限的循環(huán)。類(lèi)似于下面的:

  • while(true) {Thread.Sleep(1000); }

    這種方式也沒(méi)什么太大的問(wèn)題。不過(guò),這段代碼總是要消耗CPU的計(jì)算量,雖然很少,但做為后臺(tái)任務(wù),或者說(shuō)Service,畢竟是一種消耗,而且看著不夠高大上。

    ?

    當(dāng)然如果我們需要中斷,我們也可以把這個(gè)模板改成這樣:

    namespace?consoledemo {class?Program{private?static?AutoResetEvent?_exitEvent;static?async?Task?Main(string[]?args){bool?isRuned;Mutex?mutex?=?new?Mutex(true,?"OnlyRunOneInstance",?out?isRuned);if?(!isRuned)return;_exitEvent?=?new?AutoResetEvent(false);await?DoWork(_exitEvent);_exitEvent.WaitOne();}private?static?async?Task?DoWork(AutoResetEvent?_exitEvent){/*?Your?Code?Here?*/_exitEvent.Set();}} }

    這樣就可以根據(jù)需要,來(lái)實(shí)現(xiàn)中斷程序并退出。

    六、Console應(yīng)用的其它運(yùn)行方式

    上一節(jié)介紹的Console,其實(shí)是一個(gè)應(yīng)用程序。

    在實(shí)際應(yīng)用中,Console程序跑在Linux服務(wù)器上,我們可能會(huì)有一些其它的要求:

  • 定時(shí)運(yùn)行

  • Linux上有一個(gè)Service,叫cron,是一個(gè)用來(lái)定時(shí)執(zhí)行程序的服務(wù)。

    這個(gè)服務(wù)的設(shè)定,需要另一個(gè)命令:crontab,位置在/usr/bin下。

    具體命令格式這兒不做解釋,網(wǎng)上隨便查。

  • 運(yùn)行到后臺(tái)

  • 命令后邊加個(gè)&字符即可:

    $?./command?&
  • 運(yùn)行為Service

  • 需要持續(xù)運(yùn)行的應(yīng)用,如果以Console的形態(tài)存在,則設(shè)置為Service是最好的方式。

    Linux下,設(shè)置一個(gè)應(yīng)用為Service很簡(jiǎn)單,就這么簡(jiǎn)單三步:

    第一步:在/etc/systemd/system下面,創(chuàng)建一個(gè)service文件,例如command.service:

    [Unit] #?Service的描述,隨便寫(xiě) Description=Command[Service] RestartSec=2s Type=simple #?執(zhí)行應(yīng)用的默認(rèn)用戶(hù)。應(yīng)用如果沒(méi)有特殊要求,最好別用root運(yùn)行 User=your_user_name Group=your_group_name #?應(yīng)用的目錄,絕對(duì)路徑 WorkingDirectory=your_app_folder #?應(yīng)用的啟動(dòng)路徑 ExecStart=your_app_folder/your_app Restart=always[Install] WantedBy=multi-user.target

    差不多就這么個(gè)格式。參數(shù)的詳細(xì)說(shuō)明可以去網(wǎng)上查,實(shí)際除了設(shè)置,就是運(yùn)行了一個(gè)腳本。

    第二步:把這個(gè)command.service加上運(yùn)行權(quán)限:

    #?chmod?+x?./command.service

    第三步:注冊(cè)為Service:

    #?systemctl?enable?command.service

    完成。

    為了配合應(yīng)用,還需要記住兩個(gè)命令:啟動(dòng)和關(guān)閉Service

    #?#啟動(dòng)Service #?systemctl?start?command.service #?#關(guān)閉Service #?systemctl?stop?command.service

    七、寫(xiě)在后邊的話(huà)

    今天這個(gè)文章,是因?yàn)榍皟商?#xff0c;一個(gè)兄弟跑過(guò)來(lái)問(wèn)我關(guān)于數(shù)據(jù)總線(xiàn)的實(shí)現(xiàn)方式,而想到的一個(gè)點(diǎn)。

    ?

    很多時(shí)候,大家在寫(xiě)代碼的時(shí)候,會(huì)有一種固有的思想:寫(xiě)WebAPI,就想在這個(gè)框架中把所有的內(nèi)容都實(shí)現(xiàn)了。這其實(shí)不算是一個(gè)很好的想法。WebAPI,在業(yè)務(wù)層面,就應(yīng)該只是實(shí)現(xiàn)簡(jiǎn)單的處理請(qǐng)求,返回結(jié)果的工作,而后臺(tái)任務(wù)跟這個(gè)內(nèi)容截然不同,通常它只做處理,不做返回 --- 事實(shí)上也不太好返回,要么客戶(hù)端等待時(shí)間太長(zhǎng),要么客戶(hù)端已經(jīng)斷掉了。換句話(huà)說(shuō),用WebAPI實(shí)現(xiàn)總線(xiàn),絕不是一個(gè)好的方式。

    不過(guò),Console運(yùn)行為Service,倒是一個(gè)總線(xiàn)應(yīng)用的絕好方式。如果需要按序執(zhí)行,可以配合MQ服務(wù)器,例如RabbitMQ,來(lái)實(shí)現(xiàn)消息的按序處理。

    ?

    再說(shuō)代碼。很多需求,本來(lái)可以用很簡(jiǎn)單的方式實(shí)現(xiàn)。模式這個(gè)東西,用來(lái)面試,用來(lái)講課,都是很好的內(nèi)容,但實(shí)際開(kāi)發(fā)中,如果有更簡(jiǎn)單更有效的方式,用起來(lái)!Coding的工作是實(shí)現(xiàn),而不是秀技術(shù)。當(dāng)然,能否找到簡(jiǎn)單有效的方式,這個(gè)可能跟實(shí)際的技術(shù)面有關(guān)系。但這并不是一個(gè)不能跨越的坎。

    多看,多想,每天成長(zhǎng)一點(diǎn)點(diǎn)!

    ?

    今天的代碼,在:https://github.com/humornif/Demo-Code/tree/master/0012/demo

    (全文完)

    點(diǎn)「在看」,讓更多人因你而受益

    ↘ ?↘ ?↘

    總結(jié)

    以上是生活随笔為你收集整理的一文说通Dotnet Core的后台任务的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。