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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Asp.net core使用MediatR进程内发布/订阅

發(fā)布時間:2023/12/4 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Asp.net core使用MediatR进程内发布/订阅 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1、背景

  最近,一個工作了一個月的同事離職了,所做的東西懟了過來。一看代碼,慘不忍睹,一個方法六七百行,啥也不說了吧,實在沒法兒說。介紹下業(yè)務場景吧,一個公共操作A,業(yè)務中各個地方都會做A操作,正常人正常思維應該是把A操作提取出來封裝,其他地方調(diào)用,可這哥們兒偏偏不這么干,代碼到處復制。仔細分析了整個業(yè)務之后,發(fā)現(xiàn)是一個典型的事件/消息驅(qū)動型,或者叫發(fā)布/訂閱型的業(yè)務邏輯。鑒于系統(tǒng)是單體的,所以想到利用進程內(nèi)發(fā)布/訂閱的解決方案。記得很久之前,做WPF時候,用過Prism的EventAggregator(是不是暴露年齡了。。。),那玩意兒不知道現(xiàn)在還在不在,支不支持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方法中,發(fā)布了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創(chuàng)建站點,這玩意兒非常耗時,大概要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請求,然后調(diào)用Service1的Method,service1的Method又發(fā)布消息,消息處理器接收到消息,調(diào)用Service2的Method完成后續(xù)操作。我們運行起來看下:

  

http請求開始到結束,耗時5s,看似沒問題。我們看系統(tǒng)輸出日志:

?Service2的Method方法也確實被訂閱執(zhí)行了。

3.問題

  上述一切的一切,看似沒問題。運行成功沒?成功了。對不對?好像也對。有沒問題?大大的問題!HTTP從開始到結束,要耗時5s,實際項目中,那是一分鐘,這整整一分鐘,你要前端掛起等待么一直?理論上,這種耗時的后端操作,合理做法是HTTP迅速響應前端,并返給前端業(yè)務ID,前端根據(jù)此業(yè)務ID長輪詢后端查詢操作結果狀態(tài),直至此操作完成,決不能一直卡死的,否則交互效果不說,超過一定時間,HTTP請求會直接超時的!這就必須動刀子了,將Service2操作后臺任務化且不等待。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;
}

見注釋前后,改進地方只有一處,發(fā)布事件代碼去掉了await,這樣系統(tǒng)發(fā)布事件之后,便不會等待Service2而是繼續(xù)運行并立刻響應HTTP請求。好,我們再來運行看下效果:

?我們看到,系統(tǒng)立即響應了HTTP請求(22:40:15),5s之后,Service2才執(zhí)行完成(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;
}
}

里邊就一個屬性,當前上下文用戶,并實現(xiàn)了Dispose模式,并且當前上下文被釋放時,對該上下文對象任何操作將引發(fā)異常。從上文的Service1及Service2截圖中,我們看到了,兩個服務均注入了這個context對象,Service1設置,Service2中獲取。現(xiàn)在我們將Service2的Method方法稍作調(diào)整,如下:

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

  調(diào)整只有一處,就是獲取當前上下文用戶的操作,從5s延時之前,放到了5s延時之后。我們再來看看效果:

http請求上看,貌似沒問題,立即響應了,是吧。我們再看看程序日志輸出:

WFT!Service2 Method沒成功執(zhí)行,給了我一個異常。我們看看這個異常:

?Context dispose異常,就是說上下文這時候已經(jīng)被釋放掉,對它任何操作都無效并引發(fā)異常。很容易想到,這里就是為了模擬DBContext這種通常為Scope類型的對象生命周期,這種吊毛它就這樣。為啥會釋放?因為HTTP請求結束那會兒,core運行時就會Dispose相應scope類型對象(注意,釋放,不一定是銷毀,具體銷毀時間不確定)。那么,怎么解決?如果對基于DI生命周期比較熟悉,就會知道,這兒應該基于HTTP 的Scope之外,單獨起一個Scope了,兩個scope互補影響,HTTP對應的scope結束,另外的照常運行。我們將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中單獨起了一個Scope。我們再看運行效果:

OK,HTTP請求23:02:58響應,Service2 Method 23:03:03執(zhí)行完成。至此,問題才算得到解決。

順便提一下,大家注意看截圖,當前用戶null,因為scope之后,原來的設置過CurrentUser的context已經(jīng)釋放掉了,新開的scope中注入的context是另外的,所以沒任何信息。這里你可能會問了,那我確實需要傳遞上下文怎么辦?答案是,訂閱事件,本文中SomeEvent未定義任何信息,如果你需要傳遞,做對應調(diào)整即可,比較簡單,也不是重點,不做贅述。

4、總結

  感覺,沒什么好總結的。扎實,細心,實踐,沒什么解決不了的。

原文地址:https://www.cnblogs.com/guokun/p/11001052.html

.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?

總結

以上是生活随笔為你收集整理的Asp.net core使用MediatR进程内发布/订阅的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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