溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

發(fā)布時間:2021-06-08 16:24:03 來源:億速云 閱讀:145 作者:Leah 欄目:開發(fā)技術(shù)

本篇文章為大家展示了怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

2.Demo代碼

Startup服務注冊:

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

服務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至關(guān)重要,待會兒會詳述。

再看事件訂閱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("結(jié)束時間:{0}", DateTime.Now);
      sb.AppendLine();

      return sb.ToString();
    }

至此,Demo要干的事情,脈絡應該很清晰了:控制器接收HTTP請求,然后調(diào)用Service1的Method,service1的Method又發(fā)布消息,消息處理器接收到消息,調(diào)用Service2的Method完成后續(xù)操作。我們運行起來看下:

  怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

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

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

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

3.問題

上述一切的一切,看似沒問題。運行成功沒?成功了。對不對?好像也對。有沒問題?大大的問題!HTTP從開始到結(jié)束,要耗時5s,實際項目中,那是一分鐘,這整整一分鐘,你要前端掛起等待么一直?理論上,這種耗時的后端操作,合理做法是HTTP迅速響應前端,并返給前端業(yè)務ID,前端根據(jù)此業(yè)務ID長輪詢后端查詢操作結(jié)果狀態(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請求。好,我們再來運行看下效果:

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

我們看到,系統(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中獲取?,F(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延時之后。我們再來看看效果:

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

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

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

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

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

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

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能

上述內(nèi)容就是怎么在Asp.net core中利用MediatR實現(xiàn)進程內(nèi)發(fā)布/訂閱功能,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI