【问题标题】:Start extremely long running processes through a REST request通过 REST 请求启动超长时间运行的进程
【发布时间】:2019-06-06 08:19:47
【问题描述】:

我在一家自动化公司工作,因此我们为工业自动化创建流程。以前这种自动化是在机器方面完成的,但我们正在慢慢过渡到使用 c# 控制机器。

在我目前的项目中,一天的制作大约需要 2 个小时。工厂的操作员有一个我们在 c# 中使用 asp.net core MVC 创建的 Web 界面,他们可以在其中启动/暂停/停止这个生产过程。

在启动过程时,我们在控制器中等待一个函数,该函数基本上是一个 while 循环,用于控制这个长达 2 小时的生产过程。

现在的问题是,当我发出 REST 请求开始生产时,这个请求需要 2 小时才能完成,我希望这个请求立即完成,并且生产过程在我的 asp.net 核心应用程序的后台开始。

首先我认为我可以省略等待,只需在我的控制器中执行此操作(简化代码):

_ = _productionController.StartLongProcess(); // This contains the while loop
return Ok();

但是由于 _productionController 是作用域的,并且它的所有依赖项也是如此,所以当方法返回时,它们会立即被处理掉,例如,我不能再访问我的数据库了。

该流程应该能够不断地与我们的数据库通信,以保存有关生产流程的信息,以防万一出现故障,这样我们就可以始终从中断的地方继续。

我现在向您提出的问题是,我们是否以错误的方式解决这个问题?我认为在 asp.net 控制器中启动这些长时间运行的进程是一种不好的做法。

如何确保在这个长时间运行的进程中始终可以访问我的 DatabaseContext,即使 REST 请求已经结束。仅为此方法创建单独的范围?

【问题讨论】:

  • 如果您使用 Db 上下文进行异步调用,那么由于调用进入后台,因此它不是对系统资源的阻塞操作,这就是进行任何异步调用的方式
  • 这些在方法返回时立即被处理掉:创建一个ProcessManager类,将其注册为服务,将该服务注入控制器,通过@987654323启动进程@ .由于这个ProcessManager是由DI容器管理的,所以控制器动作返回时不会被释放。
  • @itminus 您将使用哪个 DI 容器设置/配置来避免 Dispose,它肯定会管理对象生命周期,但不会发布调用返回,除非您将其设为单例,但这不是这是个好主意

标签: c# rest asp.net-core


【解决方案1】:

从 ASP.NET Core 2.1 开始,正确的做法(在 asp.net 中)是扩展 BackgroundService(或实现 IHostedService)。

传入的请求可以告诉后台服务启动长时间运行的操作并立即返回。您当然需要处理发送重复请求或在现有请求完成之前发送新请求的情况。

文档页面有一个exampleBackgroundService 读取队列的命令并一次处理一个。

如何确保在这个长时间运行的进程中始终可以访问我的 DatabaseContext,即使 REST 请求已经结束。仅为此方法创建单独的范围?

是的,创建一个单独的范围。

我现在向您提出的问题是,我们是否以错误的方式解决这个问题?我认为在 asp.net 控制器中启动这些长时间运行的进程是一种不好的做法。

我们过去也做过类似的事情。只要容错(特别是 w.r.t. 应用程序重启)和幂等性内置到长时间运行操作的逻辑中,您就可以开始了。

【讨论】:

  • 后台服务是ASP.Net core中一个很好的补充,但我怀疑它是否提供了自定义管理的选项,它更多的是在应用程序加载到Web服务器时启动后台进程,它会自动在后台进行,但在这种情况下,OP 需要更多地控制后台进程的执行,并且想知道运行状态
  • @MrinalKamboj 是什么让你觉得这很难实现?
  • 发布的示例没有清楚地提供建议的信息。从文档中看,这些是后台服务,无需太多用户干预即可运行,如果用例需要向客户端提供持续的状态反馈,那么似乎没有现成的选项。如果你能指出一个例子或提供一个代码会很棒
  • @MrinalKamboj 链接的示例在 IMO 中足够清楚。如果您有具体问题,请告诉我,我会尽力回答。至于报告反馈的开箱即用选项 - 不存在,这是 IMO 的一件好事。该服务可以发出事件,或公开可以轮询的私有设置属性等。将 IHostedService 视为类似于 WebHost 的东西(但没有网络服务器)。 WebHost 有应用程序循环,这里我们自己编写,这是唯一的区别。 OP 想从 HTTP 请求中卸载一个任务,这回答了它。
【解决方案2】:

REST 请求预计会很短,最多几秒钟。 因此,这里的最佳做法是将长时间运行的任务卸载到后台服务并返回一个令牌,如果操作已经完成,您可以在其中轮询服务。

后台服务可以是 Net Core 中的 BackGroundWorker。这很简单,但不是真正的容错,所以某种数据库和重试逻辑可能会很好。

如果您在 Intranet 中,您也可以迁移到像 RabbitMQ 这样的固有异步协议,您可以在其中发送 StartOperation 消息,然后在流程完成后接收 Started 消息。

【讨论】:

  • Rest 调用可以是长异步操作,通过 SignalR 通知,因此是非阻塞的。消息队列是个好主意,但它也需要通知客户端完成,这就是完整的循环
  • 我什至建议将进程移至 Azure 函数,该函数可以通过 http 请求进行调度或调用。完成后您可以发送通知
  • @richardterris Azure 函数不能运行数小时,它们旨在用于短期快速操作
  • @MrinalKamboj 在最基本的层面上,客户端的 HTTP 调用尚未完成,直到请求从服务器返回。 OfC 你可以在此之上构建一个异步协议,但我经常不喜欢这样 - 使用完整的异步协议感觉更自然。
  • @ChristianSauer 用于同步调用,异步调用实际上不需要等待它们在响应可用时得到通知
【解决方案3】:

另一种选择是使用 Hangfire。它将允许您将要执行的工作排队到持久存储,例如SQL Server、MSMQ、Redis,具体取决于您的基础架构中的内容。然后,该作业将由一个也可以在 ASP.NET 进程或 Windows 服务中运行的工作人员接手。它也是分布式的,因此您可以运行多个工人实例。还支持重试失败的作业,并有一个仪表板来查看作业。最重要的是,它是免费的!

var jobId = BackgroundJob.Enqueue(() => ExecuteLongRunningProcess(parameter1));

https://www.hangfire.io/

【讨论】:

    【解决方案4】:

    以下是我对您发布的问题的理解:

    1. 您想通过 Rest api 调用发起一个长时间运行的调用
    2. 您想使用 Async 调用,但不确定如何为长时间运行的调用维护数据库上下文,该调用用于在操作期间定期进行数据库通信

    几个要点:

    1. 大多数情况下,您不清楚异步调用的工作方式
    2. 当您进行异步调用时,它会使用状态机存储当前线程同步上下文以保持连续性,它不会阻塞任何线程池线程,它利用基于硬件的并发性
    3. 可以在后端使用ConfigureAwait(false) 以避免在当前同步上下文中显式重新进入,这对性能更好
    4. 只有挑战异步调用才能成为真正的异步,整个链需要从入口点启用异步,否则无法获得好处,如果您在任何地方使用Task.WaitTask.Result,实际上甚至可能导致ASP.Net 中的死锁

    关于长时间运行,以下是选项

    1. 上面建议的简单异步调用,虽然它可以帮助您进行大量异步调用(因此可扩展性),但如果客户端离开并且无法恢复操作状态,上下文将会丢失
    2. 你可以发出一个fire and forget call,并使用像ASP.Net SignalR这样的机制,它就像网络上的IObservable,可以帮助在处理完成时通知客户端
    3. 最好的选择是使用像 Rabbit MQ 这样的消息队列,它不会冒客户端宕机的风险,它充当生产者消费者并且可以在客户端启动时通知,在这种情况下,MQ 可以在过程完成,因此可以通知客户。 MQ 可以异步方式用于传入和响应消息

    如果客户端想要定期出现并检查请求的状态,那么数据库持久性很重要,可以定期更新,可以检查长时间运行的进程的状态。

    【讨论】:

      【解决方案5】:

      我现在向您提出的问题是,我们是否以错误的方式解决这个问题?我认为在 asp.net 控制器中启动这些长时间运行的进程是一种不好的做法。

      一般来说,是的。理想情况下,ASP.NET 服务内部没有任何长时间运行的进程 - 或者至少没有无法轻松快速关闭的长时间运行的进程。

      adding a durable queue with a separate background processor 最可靠地实现了在 HTTP 请求(即请求外部代码)之外的工作。 “后台处理器”可以是 Win32 服务,甚至可能在同一台机器上。在这种架构下,HTTP 请求将请求放入队列,处理器从队列中提取请求并执行它们。

      【讨论】:

        猜你喜欢
        • 2020-07-02
        • 2017-01-29
        • 2017-02-06
        • 1970-01-01
        • 2011-08-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多