Asp.net core利用MediatR進(jìn)程內(nèi)發(fā)布/訂閱詳解
1、背景
最近,一個(gè)工作了一個(gè)月的同事離職了,所做的東西懟了過來。一看代碼,慘不忍睹,一個(gè)方法六七百行,啥也不說了吧,實(shí)在沒法兒說。介紹下業(yè)務(wù)場(chǎng)景吧,一個(gè)公共操作A,業(yè)務(wù)中各個(gè)地方都會(huì)做A操作,正常人正常思維應(yīng)該是把A操作提取出來封裝,其他地方調(diào)用,可這哥們兒偏偏不這么干,代碼到處復(fù)制。仔細(xì)分析了整個(gè)業(yè)務(wù)之后,發(fā)現(xiàn)是一個(gè)典型的事件/消息驅(qū)動(dòng)型,或者叫發(fā)布/訂閱型的業(yè)務(wù)邏輯。鑒于系統(tǒng)是單體的,所以想到利用進(jìn)程內(nèi)發(fā)布/訂閱的解決方案。記得很久之前,做WPF時(shí)候,用過Prism的EventAggregator(是不是暴露年齡了。。。),那玩意兒不知道現(xiàn)在還在不在,支不支持core,目前流行的是MediatR,跟core的集成也好,于是決定采用MediatR。
2.Demo代碼
Startup服務(wù)注冊(cè):
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddScoped<IService1, Service1>();
services.AddScoped<IService2, Service2>();
services.AddScoped<IContext, Context>();
services.AddMediatR(typeof(SomeEventHandler).Assembly);
}
服務(wù)1:
public class Service1 : IService1
{
private readonly ILogger _logger;
private readonly IMediator _mediator;
private readonly IContext _context;
private readonly IService2 _service2;
public Service1(ILogger<Service1> logger,
IMediator mediator,
IContext context)
{
_logger = logger;
_mediator = mediator;
_context = context;
//_service2 = service2;
}
public async Task Method()
{
_context.CurrentUser = "test";
//await _service2.Method();
//_service2.Method();
await _mediator.Publish(new SomeEvent());
//_mediator.Publish(new SomeEvent());
await Task.CompletedTask;
}
}
可以看到,在服務(wù)1的method方法中,發(fā)布了SomeEvent事件消息。
服務(wù)2代碼:
public class Service2 : IService2
{
private readonly ILogger _logger;
private readonly IContext _context;
public Service2(ILogger<Service2> logger,
IContext context)
{
_logger = logger;
_context = context;
}
public async Task Method()
{
_logger.LogDebug("當(dāng)前用戶:{0}", _context.CurrentUser);
await Task.Delay(5000);
//_logger.LogDebug("當(dāng)前用戶:{0}", _context.CurrentUser);
_logger.LogDebug("Service2 Method at :{0}", DateTime.Now);
}
}
解釋下,為啥服務(wù)2 Method方法中,要等待5秒,因?yàn)閷?shí)際項(xiàng)目中,有這么一個(gè)操作,把一個(gè)壓縮程序包傳遞到遠(yuǎn)端,然后在遠(yuǎn)端代碼操作IIS創(chuàng)建站點(diǎn),這玩意兒非常耗時(shí),大概要1分多鐘,這里我用5s模擬,意思意思。這個(gè)5s至關(guān)重要,待會(huì)兒會(huì)詳述。
再看事件訂閱Handler:
public class SomeEventHandler : INotificationHandler<SomeEvent>, IDisposable
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly IService2 _service2;
public SomeEventHandler(ILogger<SomeEventHandler> logger,
IServiceProvider serviceProvider,
IService2 service2)
{
_logger = logger;
_serviceProvider = serviceProvider;
_service2 = service2;
}
public void Dispose()
{
_logger.LogDebug("Handler disposed at :{0}", DateTime.Now);
}
public async Task Handle(SomeEvent notification, CancellationToken cancellationToken)
{
await _service2.Method();
//using (var scope = _serviceProvider.CreateScope())
//{
// var service2 = scope.ServiceProvider.GetService<IService2>();
// await service2.Method();
//}
}
}
然后,我們的入口Action:
[HttpGet("test")]
public async Task<ActionResult<string>> Test()
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("開始時(shí)間:{0}", DateTime.Now);
sb.AppendLine();
await _service1.Method();
sb.AppendFormat("結(jié)束時(shí)間:{0}", DateTime.Now);
sb.AppendLine();
return sb.ToString();
}
至此,Demo要干的事情,脈絡(luò)應(yīng)該很清晰了:控制器接收HTTP請(qǐng)求,然后調(diào)用Service1的Method,service1的Method又發(fā)布消息,消息處理器接收到消息,調(diào)用Service2的Method完成后續(xù)操作。我們運(yùn)行起來看下:
http請(qǐng)求開始到結(jié)束,耗時(shí)5s,看似沒問題。我們看系統(tǒng)輸出日志:
Service2的Method方法也確實(shí)被訂閱執(zhí)行了。
3.問題
上述一切的一切,看似沒問題。運(yùn)行成功沒?成功了。對(duì)不對(duì)?好像也對(duì)。有沒問題?大大的問題!HTTP從開始到結(jié)束,要耗時(shí)5s,實(shí)際項(xiàng)目中,那是一分鐘,這整整一分鐘,你要前端掛起等待么一直?理論上,這種耗時(shí)的后端操作,合理做法是HTTP迅速響應(yīng)前端,并返給前端業(yè)務(wù)ID,前端根據(jù)此業(yè)務(wù)ID長(zhǎng)輪詢后端查詢操作結(jié)果狀態(tài),直至此操作完成,決不能一直卡死的,否則交互效果不說,超過一定時(shí)間,HTTP請(qǐng)求會(huì)直接超時(shí)的!這就必須動(dòng)刀子了,將Service2操作后臺(tái)任務(wù)化且不等待。Service1的Method代碼調(diào)整如下:
public async Task Method()
{
_context.CurrentUser = "test";
//await _service2.Method();
//_service2.Method();
//await _mediator.Publish(new SomeEvent());
_mediator.Publish(new SomeEvent());
await Task.CompletedTask;
}
見注釋前后,改進(jìn)地方只有一處,發(fā)布事件代碼去掉了await,這樣系統(tǒng)發(fā)布事件之后,便不會(huì)等待Service2而是繼續(xù)運(yùn)行并立刻響應(yīng)HTTP請(qǐng)求。好,我們?cè)賮磉\(yùn)行看下效果:
我們看到,系統(tǒng)立即響應(yīng)了HTTP請(qǐng)求(22:40:15),5s之后,Service2才執(zhí)行完成(22:40:20)??此朴譀]問題了。那是不是真的沒問題呢?我們注意,Service1和Service2中,都注入了一個(gè)Context上下文對(duì)象,這個(gè)對(duì)象是我用來模擬一些Scope類型對(duì)象,例如DBContext的,代碼如下:
public class Context : IContext, IDisposable
{
private bool _isDisposed = false;
private string _currentUser;
public string CurrentUser
{
get
{
if (_isDisposed)
{
throw new Exception("Context disposed");
}
return _currentUser;
}
set
{
if (_isDisposed)
{
throw new Exception("Context disposed");
}
_currentUser = value;
}
}
public void Dispose()
{
_isDisposed = true;
}
}
里邊就一個(gè)屬性,當(dāng)前上下文用戶,并實(shí)現(xiàn)了Dispose模式,并且當(dāng)前上下文被釋放時(shí),對(duì)該上下文對(duì)象任何操作將引發(fā)異常。從上文的Service1及Service2截圖中,我們看到了,兩個(gè)服務(wù)均注入了這個(gè)context對(duì)象,Service1設(shè)置,Service2中獲取?,F(xiàn)在我們將Service2的Method方法稍作調(diào)整,如下:
public async Task Method()
{
//_logger.LogDebug("當(dāng)前用戶:{0}", _context.CurrentUser);
await Task.Delay(5000);
_logger.LogDebug("當(dāng)前用戶:{0}", _context.CurrentUser);
_logger.LogDebug("Service2 Method at :{0}", DateTime.Now);
}
調(diào)整只有一處,就是獲取當(dāng)前上下文用戶的操作,從5s延時(shí)之前,放到了5s延時(shí)之后。我們?cè)賮砜纯葱Ч?/p>
http請(qǐng)求上看,貌似沒問題,立即響應(yīng)了,是吧。我們?cè)倏纯闯绦蛉罩据敵觯?/p>
WFT!Service2 Method沒成功執(zhí)行,給了我一個(gè)異常。我們看看這個(gè)異常:
Context dispose異常,就是說上下文這時(shí)候已經(jīng)被釋放掉,對(duì)它任何操作都無效并引發(fā)異常。很容易想到,這里就是為了模擬DBContext這種通常為Scope類型的對(duì)象生命周期,這種吊毛它就這樣。為啥會(huì)釋放?因?yàn)镠TTP請(qǐng)求結(jié)束那會(huì)兒,core運(yùn)行時(shí)就會(huì)Dispose相應(yīng)scope類型對(duì)象(注意,釋放,不一定是銷毀,具體銷毀時(shí)間不確定)。那么,怎么解決?如果對(duì)基于DI生命周期比較熟悉,就會(huì)知道,這兒應(yīng)該基于HTTP 的Scope之外,單獨(dú)起一個(gè)Scope了,兩個(gè)scope互補(bǔ)影響,HTTP對(duì)應(yīng)的scope結(jié)束,另外的照常運(yùn)行。我們將Handler處調(diào)整如下:
public async Task Handle(SomeEvent notification, CancellationToken cancellationToken)
{
//await _service2.Method();
using (var scope = _serviceProvider.CreateScope())
{
var service2 = scope.ServiceProvider.GetService<IService2>();
await service2.Method();
}
}
無非就是Handle中單獨(dú)起了一個(gè)Scope。我們?cè)倏催\(yùn)行效果:
OK,HTTP請(qǐng)求23:02:58響應(yīng),Service2 Method 23:03:03執(zhí)行完成。至此,問題才算得到解決。
順便提一下,大家注意看截圖,當(dāng)前用戶null,因?yàn)閟cope之后,原來的設(shè)置過CurrentUser的context已經(jīng)釋放掉了,新開的scope中注入的context是另外的,所以沒任何信息。這里你可能會(huì)問了,那我確實(shí)需要傳遞上下文怎么辦?答案是,訂閱事件,本文中SomeEvent未定義任何信息,如果你需要傳遞,做對(duì)應(yīng)調(diào)整即可,比較簡(jiǎn)單,也不是重點(diǎn),不做贅述。
4、總結(jié)
感覺,沒什么好總結(jié)的。扎實(shí),細(xì)心,實(shí)踐,沒什么解決不了的。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)我們的支持。
上一篇:WCF如何綁定netTcpBinding寄宿到控制臺(tái)應(yīng)用程序詳解
欄 目:ASP.NET
下一篇:.NET CORE中比較兩個(gè)文件內(nèi)容是否相同的最快方法
本文標(biāo)題:Asp.net core利用MediatR進(jìn)程內(nèi)發(fā)布/訂閱詳解
本文地址:http://www.jygsgssxh.com/a1/ASP_NET/10915.html
您可能感興趣的文章
- 01-11如何給asp.net core寫個(gè)簡(jiǎn)單的健康檢查
- 01-11淺析.Net Core中Json配置的自動(dòng)更新
- 01-11.net core高吞吐遠(yuǎn)程方法如何調(diào)用組件XRPC詳解
- 01-11.NET Core 遷移躺坑記續(xù)集之Win下莫名其妙的超時(shí)
- 01-11docker部署Asp.net core應(yīng)用的完整步驟
- 01-11.net core webapi jwt 更為清爽的認(rèn)證詳解
- 01-11ASP.NET Core靜態(tài)文件的使用方法
- 01-11.NET Core 3.0之創(chuàng)建基于Consul的Configuration擴(kuò)展組件
- 01-11.net core EF Core調(diào)用存儲(chǔ)過程的方式
- 01-11asp.net Core3.0區(qū)域與路由配置的方法


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-11vscode extension插件開發(fā)詳解
- 01-11VsCode插件開發(fā)之插件初步通信的方法
- 01-11如何給asp.net core寫個(gè)簡(jiǎn)單的健康檢查
- 01-11.net core高吞吐遠(yuǎn)程方法如何調(diào)用組件
- 01-11淺析.Net Core中Json配置的自動(dòng)更新
- 01-11.NET開發(fā)人員關(guān)于ML.NET的入門學(xué)習(xí)
- 01-11.NET Core 遷移躺坑記續(xù)集之Win下莫名其
- 01-11.net core webapi jwt 更為清爽的認(rèn)證詳解
- 01-11docker部署Asp.net core應(yīng)用的完整步驟
- 01-11ASP.NET Core靜態(tài)文件的使用方法
隨機(jī)閱讀
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 04-02jquery與jsp,用jquery
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什


