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进程内发布/订阅相关推荐

  1. [翻译] ASP.NET Core 2.2 正式版发布

    本文为翻译,原文地址:https://blogs.msdn.microsoft.com/webdev/2018/12/04/asp-net-core-2-2-available-today/ 我(文章 ...

  2. Asp.Net Core 2.2.0-preview1已经发布

    原文地址 ASP.NET Core 2.2.0-preview1 now available 今天我们很高兴地宣布,现在可以试用ASP.NET Core和.NET Core的下一个次要版本的第一个预览 ...

  3. ASP.NET Core 进程内(InProcess)托管(6)《从零开始学ASP.NET CORE MVC》:

    本文出自<从零开始学ASP.NET CORE MVC> 推荐文章:ASP.NET Core 中的 Main方法 ASP.NET Core 进程内(InProcess)托管 在这个视频中我们 ...

  4. asp.net core 系列 18 web服务器实现

    一. ASP.NET Core Module 在介绍ASP.NET Core Web实现之前,先来了解下ASP.NET Core Module.该模块是插入 IIS 管道的本机 IIS 模块(本机是指 ...

  5. ASP.NET Core on K8s学习之旅(14)Ingress灰度发布

    [云原生]| 作者/Edison Zhou 这是恰童鞋骚年的第236篇原创文章 上一篇介绍了Ingress的基本概念和Nginx Ingress的基本配置和使用,然后我还录了一个快速分享小视频介绍了一 ...

  6. ASP.NET Core 2.0 MVC 发布部署--------- ASP.NET Core 发布的具体操作

    ASP.NET Core 发布的具体操作 下面使用C# 编写的ASP.NET Core Web项目示例说明发布的全过程. 1.创建项目 选择"文件" > "新建&q ...

  7. ASP.NET Core基础补充01

    ASP.NET Core (Main方法) 为什么ASP.NET Core采用Main方法? 需要记住的最重要的一点是,ASP.NET Core Web 应用程序最初作为控制台应用程序启动,Main( ...

  8. 用ASP.NET Core构建可检测的高可用服务--学习笔记

    摘要 随着现代化微服务架构的发展,系统故障的定位与快速恢复面临着诸多挑战,构建可检测的服务,帮助线上保障团队时刻掌控应用的运行状况越来越重要.本次分享会讲解如何让 ASP .NET Core 应用与现 ...

  9. Azure 上使用 Windows Server Core 运行 ASP.NET Core 网站

    点击上方蓝字关注"汪宇杰博客" 导语 微软智慧云 Azure 上虽然早就有 App Service 这种完全托管的 PaaS 服务可以让我们分分钟建网站.但是不自己配一下环境,就不 ...

最新文章

  1. 植物微生物组专题:研究方法、当前热点及未来方向
  2. HTML5 localStorage本地存储
  3. 「镁客·请讲」虚之实康成:等风来不如先发制人,打磨好硬件产品才是王道...
  4. python定义数列每项的变量__Python定义方法
  5. python处理二进制文件_使用Python进行二进制文件读写的简单方法(推荐)
  6. 数据库保存经纬度,需要采用什么数据类型,小数点应该精确多少位?
  7. Flutter之RenderView RenderObject ParentData知识点梳理
  8. 背包笔记及Java实现
  9. php的链接查询,php – 使用指向另一个查询的链接运行查询.
  10. python基本规则语法
  11. php setlocale 中国,PHP setlocale无效
  12. 智能电子后视镜MFC01-LCD 高配版规格书(AI-CMS)
  13. Spring Cloud使用Dubbo实现Rpc调用失败之配置错误
  14. Uploadifive上传
  15. 【华为OD机试真题】促销活动(货币兑换)100%通过率
  16. php的知识体系结构图,高中英语全部知识体系结构图汇总
  17. python中的序列类型数据结构元素的切片操作_PythonI/O进阶学习笔记_4.自定义序列类(序列基类继承关系/可切片对象/推导式)...
  18. 纪念海子离去20周年
  19. com.github.abel533.mapper.MapperProvider插件使用遇到的问题
  20. 算法C++ DepthFirstSearch BreadthFirstSearch代码模式示范实现(第四章)

热门文章

  1. 将Teams app升级到net6
  2. windows7黑屏修复_如何在Windows 10更新后修复黑屏
  3. JavaScript基础和js概括
  4. 配置 mybatis的 log4j.properties
  5. 关于ubuntu 14.04 dpkg 问题
  6. 解决ubuntu下eclipse 经常崩溃的问题
  7. Ubuntu 12.04 x64 下安装 GStreamer+FFmpeg+Opencv
  8. 【Blog.Core开源】将Program升级为.NET6.0版本
  9. 阻止你变现的,从来都不是开源许可证
  10. c#winform自定义窗体(含源码)