宝哥软件园

Asp.net核心利用调解人进程内发布/订阅详解

编辑:宝哥软件园 来源:互联网 时间:2021-08-22

1、背景

最近,一个工作了一个月的同事离职了,所做的东西怼了过来。一看代码,惨不忍睹,一个方法六七百行,啥也不说了吧,实在没法儿说。介绍下业务场景吧,一个公共操作一个,业务中各个地方都会做A操作,正常人正常思维应该是把A操作提取出来封装,其他地方调用,可这哥们儿偏偏不这么干,代码到处复制。仔细分析了整个业务之后,发现是一个典型的事件/消息驱动型,或者叫发布/订阅型的业务逻辑。鉴于系统是单体的,所以想到利用进程内发布/订阅的解决方案。记得很久之前,做数据绑定时候,用过棱镜的事件聚合器(是不是暴露年龄了。),那玩意儿不知道现在还在不在,支不支持核心,目前流行的是MediatR,跟核心的集成也好,于是决定采用调解人。

2.演示代码

启动服务注册:

public void ConfigureServices(IServiceCollection services){ services .AddMvc().SetCompatibilityVersion(兼容性版本. version _ 2 _ 2);服务AddScopedIService1,service 1();服务AddScopedIService2,service 2();服务AddScopedIContext,Context();服务AddMediatR(类型为(SomeEventHandler)).装配);}服务1:

公共类服务1 : iseservice 1 { private readonly ILogger _ logger;private readonly IMediator _ mediator;私有只读IContext _ context private readonly iseservice 2 _ service 2;公共服务1(ilogageservice1记录器IMediator中介器、图标文本上下文){ _ logger=logger _ mediator=mediator _ context=上下文;//_ service 2=service 2;}公共异步任务方法(){ _context .CurrentUser=' test//await _ service 2 .method();//_service2 .method();等待调解人.publish(new some vent());//_调解人发布(new some vent());等待任务。完成任务;} }可以看到,在服务一的方法方法中,发布了一些事件事件消息。

服务2代码:

公共类service 2 : iseservice 2 { private readonly ILogger _ logger;私有只读IContext _ context public service 2(ilogageservice2记录器,图标文本上下文){ _ logger=logger_context=上下文;}公共异步任务方法(){ _logger ."日志调试("当前用户:{0} ',上下文. CurrentUser);等待任务。延迟(5000);//_记录器。"日志调试("当前用户:{0} ',上下文. CurrentUser);_记录器.日志调试(' :{0}处的服务2方法),日期时间。现在);} }解释下,为啥服务方法2方法中,要等待5秒,因为实际项目中,有这么一个操作,把一个压缩程序包传递到远端,然后在远端代码操作(同移民检查员移民检查)创建站点,这玩意儿非常耗时,大概要一分多钟,这里我用5s模拟,意思意思。这个5s至关重要,待会儿会详述。

再看事件订阅处理程序:

公共类someventhandler : INotificationHandlerSomeEvent,IDisposable { private readonly ILogger _ logger;private readonly IServiceProvider _ ServiCeProvider;private readonly iseservice 2 _ service 2;public someventhandler(iloggersomeventhandler logger,iseservice provider service provider,iseservice 2 service 2){ _ logger=logger;_服务提供商=服务提供商;_ service 2=service 2 } public void Dispose(){ _ logger .日志调试('处理程序位于:{0} ',日期时间。现在);}公共异步任务句柄(某些事件通知,取消令牌取消令牌){ await _service2 .method();//使用(var scope=_serviceProvider .create scope())//{//var service 2=作用域服务提供商。getserviceiseservice 2();//等待服务2。method();//} } }然后,我们的入口行动:

[HttpGet(' Test ')]public async TaskActionResultstring Test(){ StringBuilder sb=new StringBuilder();某人(somebody的简写)AppendFormat('开始时间:{0} ',DateTime。现在);某人(somebody的简写)AppendLine();wait _service1。method();某人(somebody的简写)AppendFormat('结束时间:{0} ',DateTime。现在);某人(somebody的简写)AppendLine();归还某人。ToString();}此时,Demo应该做什么的上下文应该是清楚的:控制器接收HTTP请求,然后调用Service1的Method,然后service1的Method发出消息。当消息处理器收到消息时,它调用Service2的方法来完成后续操作。让我们跑过去看看:

http请求从开始到结束需要5秒钟,这似乎没有问题。让我们看看系统输出日志:

服务2的方法方法确实已被订阅和执行。

3.问题

上面的一切似乎都没问题。运行成功了吗?成功了。正确似乎是对的。有什么问题吗?大问题!HTTP从开始到结束需要5s。在实际项目中,是一分钟。这是整整一分钟。你想让前端挂起等待吗?理论上,对于这种耗时的后端操作,合理的方式是HTTP快速响应前端,返回给前端业务ID。前端根据业务ID轮询后端查询操作结果状态,在此操作完成之前,一定不要一直卡着,否则不会说交互效果,超过一定时间HTTP请求会直接超时!因此,有必要在后台使用刀具来执行Service2操作,而无需等待。服务1的方法代码调整如下:

公共异步任务方法(){ _context。CurrentUser=' test//await _service2。method();//_service2。method();//wait _ mediator。publish(new somevent());_调解员。publish(new somevent());等待任务。完成任务;}评论前后,只有一个改进。从发布事件代码中删除了await,因此在系统发布事件后,它不会等待Service2,而是继续运行并立即响应HTTP请求。好,我们再运行一次,看看效果:

我们可以看到系统立即响应了HTTP请求(22:40:15),5s后执行了service 2(22:40:20)。看来又没有问题了。真的可以吗?我们注意到服务1和服务2都被注入了一个上下文对象,我用它来模拟一些范围类型的对象,比如数据库上下文。代码如下:

公共类Context : IContext,IDisposable { private bool _ Isdisposed=false;私有字符串_ currentUser公共字符串CurrentUser { get { if(_ is disposed){引发新异常('上下文已释放');} return _ currentUser}设置{ if (_isDisposed) {引发新异常('上下文已释放');} _ currentUser=value} } public void Dispose(){ _ isDisposed=true;}}中有一个属性,即当前上下文用户,并实现了Dispose模式。当当前上下文被释放时,对上下文对象的任何操作都将引发异常。从上面Service1和Service2的截图中,我们可以看到上下文对象被注入到这两个服务中,这是Service1设置的,从Service2获取。现在我们稍微调整一下Service2的Method方法,如下:

public async task method(){//_ logger . log debug('当前用户:{0} ',_context。CurrentUser);等待任务。延迟(5000);_logger。LogDebug('当前用户:{0} ',_context。CurrentUser);_logger。日志调试(' :{0}处的Service2方法),日期时间。现在);}只有一个调整,就是获取当前上下文用户的操作,从5s前延迟到5s后延迟。我们再来看看效果:

在http请求上,似乎没有问题,它会立即响应,对吗?让我们再次查看程序日志输出:

WFT!Service2方法执行失败,这给了我一个异常。让我们看看这个异常:

Context dispose异常,也就是说此时上下文已经被释放,对它的任何操作都是无效的,抛出异常。很容易认为这是模拟DBContext的生命周期,通常是Scope类型的对象。为什么会发布?在HTTP请求结束时,核心运行时会Dispose对应的作用域类型对象(注意释放不一定是销毁,具体销毁时间不确定)。那么,如何解决呢?如果你熟悉基于DI的生命周期,你会知道除了基于HTTP的作用域之外,还应该有一个单独的作用域。两个作用域相辅相成,对应HTTP的作用域结束,其他的照常运行。我们将按如下方式调整处理器:

公共异步任务句柄(某些事件通知,取消令牌取消令牌){ //await _service2。method();使用(var scope=_serviceProvider。CreateScope()) { var service2=作用域。service provider . GetServiceIService2();等待服务2。method();}}只不过是句柄中的一个作用域。我们来看看操作效果:

好的,HTTP请求2:02:58响应,service2method 23:0:03执行。至此,问题就解决了。

对了,你要注意截图。CurrentUser为空,因为设置了CurrentUser的原始上下文已经在作用域后释放,新打开的作用域注入的上下文不同,所以没有信息。在这里你可能会问,如果我真的需要传递上下文呢?答案是,订阅活动。在本文中,SomeEvent没有定义任何信息。如果需要传输,可以做相应的调整。比较简单,不是重点,不要重复。

4.摘要

感觉,没什么好总结的。扎实、细致、实用,什么都解决不了。

好了,这就是本文的全部内容。希望本文的内容对大家的学习或工作有一定的参考价值。谢谢你的支持。

更多资讯
游戏推荐
更多+