Asp.net core使用MediatR进程内发布/订阅
1、背景
最近,一个工作了一个月的同事离职了,所做的东西怼了过来。一看代码,惨不忍睹,一个方法六七百行,啥也不说了吧,实在没法儿说。介绍下业务场景吧,一个公共操作A,业务中各个地方都会做A操作,正常人正常思维应该是把A操作提取出来封装,其他地方调用,可这哥们儿偏偏不这么干,代码到处复制。仔细分析了整个业务之后,发现是一个典型的事件/消息驱动型,或者叫发布/订阅型的业务逻辑。鉴于系统是单体的,所以想到利用进程内发布/订阅的解决方案。记得很久之前,做WPF时候,用过Prism的EventAggregator(是不是暴露年龄了。。。),那玩意儿不知道现在还在不在,支不支持core,目前流行的是MediatR,跟core的集成也好,于是决定采用MediatR。
2.Demo代码
Startup服务注册:
服务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; } }
可以看到,在服务1的method方法中,发布了SomeEvent事件消息。
服务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("当前用户:{0}", _context.CurrentUser);await Task.Delay(5000);//_logger.LogDebug("当前用户:{0}", _context.CurrentUser); _logger.LogDebug("Service2 Method at :{0}", DateTime.Now); } }
解释下,为啥服务2 Method方法中,要等待5秒,因为实际项目中,有这么一个操作,把一个压缩程序包传递到远端,然后在远端代码操作IIS创建站点,这玩意儿非常耗时,大概要1分多钟,这里我用5s模拟,意思意思。这个5s至关重要,待会儿会详述。
再看事件订阅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("开始时间:{0}", DateTime.Now); sb.AppendLine();await _service1.Method(); sb.AppendFormat("结束时间:{0}", DateTime.Now); sb.AppendLine(); return sb.ToString(); }
至此,Demo要干的事情,脉络应该很清晰了:控制器接收HTTP请求,然后调用Service1的Method,service1的Method又发布消息,消息处理器接收到消息,调用Service2的Method完成后续操作。我们运行起来看下:
http请求开始到结束,耗时5s,看似没问题。我们看系统输出日志:
Service2的Method方法也确实被订阅执行了。
3.问题
上述一切的一切,看似没问题。运行成功没?成功了。对不对?好像也对。有没问题?大大的问题!HTTP从开始到结束,要耗时5s,实际项目中,那是一分钟,这整整一分钟,你要前端挂起等待么一直?理论上,这种耗时的后端操作,合理做法是HTTP迅速响应前端,并返给前端业务ID,前端根据此业务ID长轮询后端查询操作结果状态,直至此操作完成,决不能一直卡死的,否则交互效果不说,超过一定时间,HTTP请求会直接超时的!这就必须动刀子了,将Service2操作后台任务化且不等待。Service1的Method代码调整如下:
public async Task Method() { _context.CurrentUser = "test";//await _service2.Method();//_service2.Method();//await _mediator.Publish(new SomeEvent()); _mediator.Publish(new SomeEvent()); await Task.CompletedTask; }
见注释前后,改进地方只有一处,发布事件代码去掉了await,这样系统发布事件之后,便不会等待Service2而是继续运行并立刻响应HTTP请求。好,我们再来运行看下效果:
我们看到,系统立即响应了HTTP请求(22:40:15),5s之后,Service2才执行完成(22:40:20)。看似又没问题了。那是不是真的没问题呢?我们注意,Service1和Service2中,都注入了一个Context上下文对象,这个对象是我用来模拟一些Scope类型对象,例如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; } }
里边就一个属性,当前上下文用户,并实现了Dispose模式,并且当前上下文被释放时,对该上下文对象任何操作将引发异常。从上文的Service1及Service2截图中,我们看到了,两个服务均注入了这个context对象,Service1设置,Service2中获取。现在我们将Service2的Method方法稍作调整,如下:
public async Task Method() {//_logger.LogDebug("当前用户:{0}", _context.CurrentUser);await Task.Delay(5000); _logger.LogDebug("当前用户:{0}", _context.CurrentUser); _logger.LogDebug("Service2 Method at :{0}", DateTime.Now); }
调整只有一处,就是获取当前上下文用户的操作,从5s延时之前,放到了5s延时之后。我们再来看看效果:
http请求上看,貌似没问题,立即响应了,是吧。我们再看看程序日志输出:
WFT!Service2 Method没成功执行,给了我一个异常。我们看看这个异常:
Context dispose异常,就是说上下文这时候已经被释放掉,对它任何操作都无效并引发异常。很容易想到,这里就是为了模拟DBContext这种通常为Scope类型的对象生命周期,这种吊毛它就这样。为啥会释放?因为HTTP请求结束那会儿,core运行时就会Dispose相应scope类型对象(注意,释放,不一定是销毁,具体销毁时间不确定)。那么,怎么解决?如果对基于DI生命周期比较熟悉,就会知道,这儿应该基于HTTP 的Scope之外,单独起一个Scope了,两个scope互补影响,HTTP对应的scope结束,另外的照常运行。我们将Handler处调整如下:
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中单独起了一个Scope。我们再看运行效果:
OK,HTTP请求23:02:58响应,Service2 Method 23:03:03执行完成。至此,问题才算得到解决。
顺便提一下,大家注意看截图,当前用户null,因为scope之后,原来的设置过CurrentUser的context已经释放掉了,新开的scope中注入的context是另外的,所以没任何信息。这里你可能会问了,那我确实需要传递上下文怎么办?答案是,订阅事件,本文中SomeEvent未定义任何信息,如果你需要传递,做对应调整即可,比较简单,也不是重点,不做赘述。
4、总结
感觉,没什么好总结的。扎实,细心,实践,没什么解决不了的。
原文地址:https://www.cnblogs.com/guokun/p/11001052.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
Asp.net core使用MediatR进程内发布/订阅相关推荐
- [翻译] ASP.NET Core 2.2 正式版发布
本文为翻译,原文地址:https://blogs.msdn.microsoft.com/webdev/2018/12/04/asp-net-core-2-2-available-today/ 我(文章 ...
- Asp.Net Core 2.2.0-preview1已经发布
原文地址 ASP.NET Core 2.2.0-preview1 now available 今天我们很高兴地宣布,现在可以试用ASP.NET Core和.NET Core的下一个次要版本的第一个预览 ...
- ASP.NET Core 进程内(InProcess)托管(6)《从零开始学ASP.NET CORE MVC》:
本文出自<从零开始学ASP.NET CORE MVC> 推荐文章:ASP.NET Core 中的 Main方法 ASP.NET Core 进程内(InProcess)托管 在这个视频中我们 ...
- asp.net core 系列 18 web服务器实现
一. ASP.NET Core Module 在介绍ASP.NET Core Web实现之前,先来了解下ASP.NET Core Module.该模块是插入 IIS 管道的本机 IIS 模块(本机是指 ...
- ASP.NET Core on K8s学习之旅(14)Ingress灰度发布
[云原生]| 作者/Edison Zhou 这是恰童鞋骚年的第236篇原创文章 上一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和使用,然后我还录了一个快速分享小视频介绍了一 ...
- ASP.NET Core 2.0 MVC 发布部署--------- ASP.NET Core 发布的具体操作
ASP.NET Core 发布的具体操作 下面使用C# 编写的ASP.NET Core Web项目示例说明发布的全过程. 1.创建项目 选择"文件" > "新建&q ...
- ASP.NET Core基础补充01
ASP.NET Core (Main方法) 为什么ASP.NET Core采用Main方法? 需要记住的最重要的一点是,ASP.NET Core Web 应用程序最初作为控制台应用程序启动,Main( ...
- 用ASP.NET Core构建可检测的高可用服务--学习笔记
摘要 随着现代化微服务架构的发展,系统故障的定位与快速恢复面临着诸多挑战,构建可检测的服务,帮助线上保障团队时刻掌控应用的运行状况越来越重要.本次分享会讲解如何让 ASP .NET Core 应用与现 ...
- Azure 上使用 Windows Server Core 运行 ASP.NET Core 网站
点击上方蓝字关注"汪宇杰博客" 导语 微软智慧云 Azure 上虽然早就有 App Service 这种完全托管的 PaaS 服务可以让我们分分钟建网站.但是不自己配一下环境,就不 ...
最新文章
- 植物微生物组专题:研究方法、当前热点及未来方向
- HTML5 localStorage本地存储
- 「镁客·请讲」虚之实康成:等风来不如先发制人,打磨好硬件产品才是王道...
- python定义数列每项的变量__Python定义方法
- python处理二进制文件_使用Python进行二进制文件读写的简单方法(推荐)
- 数据库保存经纬度,需要采用什么数据类型,小数点应该精确多少位?
- Flutter之RenderView RenderObject ParentData知识点梳理
- 背包笔记及Java实现
- php的链接查询,php – 使用指向另一个查询的链接运行查询.
- python基本规则语法
- php setlocale 中国,PHP setlocale无效
- 智能电子后视镜MFC01-LCD 高配版规格书(AI-CMS)
- Spring Cloud使用Dubbo实现Rpc调用失败之配置错误
- Uploadifive上传
- 【华为OD机试真题】促销活动(货币兑换)100%通过率
- php的知识体系结构图,高中英语全部知识体系结构图汇总
- python中的序列类型数据结构元素的切片操作_PythonI/O进阶学习笔记_4.自定义序列类(序列基类继承关系/可切片对象/推导式)...
- 纪念海子离去20周年
- com.github.abel533.mapper.MapperProvider插件使用遇到的问题
- 算法C++ DepthFirstSearch BreadthFirstSearch代码模式示范实现(第四章)
热门文章
- 将Teams app升级到net6
- windows7黑屏修复_如何在Windows 10更新后修复黑屏
- JavaScript基础和js概括
- 配置 mybatis的 log4j.properties
- 关于ubuntu 14.04 dpkg 问题
- 解决ubuntu下eclipse 经常崩溃的问题
- Ubuntu 12.04 x64 下安装 GStreamer+FFmpeg+Opencv
- 【Blog.Core开源】将Program升级为.NET6.0版本
- 阻止你变现的,从来都不是开源许可证
- c#winform自定义窗体(含源码)