【问题标题】:Do some work after the response in ASP.NET Core在 ASP.NET Core 响应后做一些工作
【发布时间】:2017-01-16 00:16:28
【问题描述】:

我有一个使用 EFCore 的 ASP.NET Core 网站。 我想做一些工作,比如登录数据库,但在将响应发送给用户以便更快地回答之后。

我可以在不同的线程中执行此操作,但由于 DbContext 的异步访问,我不确定它是否安全。有什么推荐的方法吗?

public async Task<IActionResult> Request([FromForm]RequestViewModel model, string returnUrl = null)
{
    try 
    {
      var newModel = new ResponseViewModel(model);
      // Some work 
      return View("RequestView",newModel)
    }
    finally
    {
        // Some analysis on the request
        // I would like to defer this part
        await Log(model);
    }
}

其中一个原因是我想调用一个网络服务(地理编码),它不需要回答,但可以很好地处理日志(我需要坐标的城市/国家)。

【问题讨论】:

  • 1.你在做什么,你的日志记录比你减慢整个事情的请求慢得多?听起来像微优化,可能根本不需要线程分配,上下文切换)。 3. 如果你真的坚持并有可靠的数据支持这是一个瓶颈,请将消息(内存或分布式)排队并在后台处理)
  • 它并不是真正的“重”,但需要对数据库进行一些读/写(例如检查请求是否已经完成),我只想在这样做之前能够回答因为它没有给用户任何东西,用户更愿意得到他的回应。

标签: entity-framework asp.net-core asp.net-core-mvc


【解决方案1】:

我看到这个问题从未得到解答,但实际上有一个解决方案。 简单的解决方案:

public async Task<IActionResult> Request([FromForm]RequestViewModel model, string returnUrl = null)
{
    try 
    {
      var newModel = new ResponseViewModel(model);
      // Some work 
      return View("RequestView",newModel)
    }
    finally
    {
        Response.OnCompleted(async () =>
        {
            // Do some work here
            await Log(model);
        });
    }
}

安全的解决方案,因为OnCompleted曾经在响应发送之前被调用,所以延迟响应:

public static void OnCompleted2(this HttpResponse resp, Func<Task> callback)
{
    resp.OnCompleted(() =>
    {
        Task.Run(() => { try { callback.Invoke(); } catch {} });
        return Task.CompletedTask;
    });
}

并致电Response.OnCompleted2(async () =&gt; { /* some async work */ })

【讨论】:

  • 只是指出从ASP.NET Core 2.1.0-preview2 开始修复了在发送响应之前调用了OnCompleted的bug,所以如果你使用的是更高版本,第一个解决方案是可以的: GitHub issue
【解决方案2】:

Jeans answertry - return - finally 模式上的question and answer 为基础,可以删除tryfinally 块(如果您真的不想捕获异常)。

这导致以下代码:

public async Task<IActionResult> Request([FromForm] RequestViewModel model, string returnUrl = null)
{
    var newModel = new ResponseViewModel(model);

    // Some work 

    Response.OnCompleted(async () =>
    {
        // Do some work here
        await Log(model);
    });

    return View("RequestView", newModel);
}

【讨论】:

    【解决方案3】:

    没有开箱即用的方式来做你想做的事。

    但是,这是一种可能的方法:

    1. 拥有一个队列和一个工作线程(线程或进程)
    2. 就在将请求发送回客户端之前,在该队列中添加一条消息
    3. 工作人员将在未来的某个时间接收该消息,并对其进行处理。

    由于工作在其他地方而不是在请求线程上运行,因此服务器可以完成请求线程并且工作人员可以完成剩下的工作。

    【讨论】:

    • 这是我正在考虑的一个选项,即使不是最优的,它也可能是最有效的。我的替代选择是记录“原始”请求并在 Windows 服务中对原始请求进行后处理,独立于 ASP...由于 EF 对线程不太友好,我不确定最佳解决方案(第二个将肯定不是问题,但需要更多的工作来设置和更新)
    【解决方案4】:

    创建一个继承自ActionFilterAttribute 的新类,覆盖OnResultExecuted 方法以执行日志记录,然后将您的属性类应用于您要执行日志记录的控制器操作。

    【讨论】:

    • 这不会带来预期的目标,因为他想在请求**完成之后进行日志记录。请求仍在运行时仍会执行 ActionFilter
    【解决方案5】:

    尝试使用 Hangfire。 Hangfire 是一种在 .NET 和 .NET Core 应用程序中执行后台处理的简单方法。无需 Windows 服务或单独的进程。 由持久存储支持。开放且免费用于商业用途。

    你可以做类似的事情

    var jobId = BackgroundJob.Enqueue(() => Log(model));
    

    这是我在 ASP.NET Core 中使用 HangFire 的 blog post

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-06-05
      • 2011-12-28
      • 1970-01-01
      • 2020-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多