简体   繁体   English

通过 REST 请求启动运行时间极长的进程

[英]Start extremely long running processes through a REST request

I'm working at an automation firm so we create processes for industrial automation.我在一家自动化公司工作,所以我们为工业自动化创建流程。 Previously this automation was done on the machine side of things, but we're slowly transitioning to controlling the machines with c#.以前这种自动化是在机器方面完成的,但我们正在慢慢过渡到使用 c# 控制机器。

On my current project the production for one day takes about 2 hours.在我目前的项目中,一天的制作大约需要 2 个小时。 The operators of the factory have a web interface that we created in c# using asp.net core MVC in which they can start/pause/stop this production process.工厂的操作员有一个我们在 c# 中使用 asp.net 核心 MVC 创建的 Web 界面,他们可以在其中启动/暂停/停止此生产过程。

When starting the process we await a function in our controller that is basically a while loop that controls this 2h long production process.当开始这个过程时,我们等待控制器中的一个函数,它基本上是一个控制这个 2 小时长的生产过程的 while 循环。

The problem is now that when I send out the REST request to start the production this request takes 2h to complete, I would prefer this request immediately completes and the production process starts on the background of my asp.net core application.现在的问题是,当我发出 REST 请求以开始生产时,此请求需要 2 小时才能完成,我希望此请求立即完成,并且生产过程在我的 asp.net 核心应用程序的后台开始。

First I thought I could just leave out the await and simply do this in my controller (simplified code):首先,我想我可以省略等待,只需在我的控制器中执行此操作(简化代码):

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

But since _productionController is scoped and all its dependencies are as well, these immediately get disposed of when the method returns and I can't access my database anymore for example.但是由于 _productionController 是有作用域的,并且它的所有依赖项也是如此,因此当方法返回时这些会立即被处理掉,例如我无法再访问我的数据库。

The process should be able to continuously talk to our database to save information about the production process in case something fails, that way we can always pick off where we left off.该流程应该能够不断地与我们的数据库对话,以保存有关生产流程的信息,以防万一出现故障,这样我们就可以从中断的地方继续进行。

My question to you is now, are we tackling this the wrong way?我现在要问你的问题是,我们是否以错误的方式解决这个问题? I imagine it's bad practice to start these long running processes in the asp.net controller.我想在 asp.net 控制器中启动这些长时间运行的进程是不好的做法。

How do I make sure I always have access to my DatabaseContext in this long running process even though the REST request has already ended.即使 REST 请求已经结束,我如何确保在这个长时间运行的过程中始终可以访问我的 DatabaseContext。 Create a separate scope only for this method?仅为此方法创建单独的范围?

Starting ASP.NET Core 2.1, the right way to do this (within asp.net) is to extend BackgroundService (or implement IHostedService ).从 ASP.NET Core 2.1 开始,执行此操作的正确方法(在 asp.net 中)是扩展BackgroundService (或实现IHostedService )。

Incoming requests can tell the background service to start the long-running operation and return immediately.传入的请求可以告诉后台服务启动长时间运行的操作并立即返回。 You'll of course need to handle cases where duplicate requests are sent in, or new requests are sent in before the existing request is completed.您当然需要处理在现有请求完成之前发送重复请求或发送新请求的情况。

The documentation page has an example where the BackgroundService reads commands of a queue and processes them one at a time.文档页面有一个示例,其中BackgroundService读取队列的命令并一次处理一个。

How do I make sure I always have access to my DatabaseContext in this long running process even though the REST request has already ended.即使 REST 请求已经结束,我如何确保在这个长时间运行的过程中始终可以访问我的 DatabaseContext。 Create a separate scope only for this method?仅为此方法创建单独的范围?

Yes, create a separate scope.是的,创建一个单独的范围。

My question to you is now, are we tackling this the wrong way?我现在要问你的问题是,我们是否以错误的方式解决这个问题? I imagine it's bad practice to start these long running processes in the asp.net controller.我想在 asp.net 控制器中启动这些长时间运行的进程是不好的做法。

We've done something similar in the past.我们过去做过类似的事情。 As long as fault-tolerance (particularly wrt app restarts) and idempotence are built into the long-running-operation's logic, you should be good to go.只要容错(尤其是 wrt 应用程序重新启动)和幂等性内置到长时间运行的操作的逻辑中,您就应该很高兴。

REST requests are expected to be short, a few seconds at maximum. REST 请求预计很短,最多几秒钟。 So best practice here would be to offload a long running task to a background service and return a token where you can poll the service if the operation has already finished.所以这里的最佳实践是将长时间运行的任务卸载到后台服务,并返回一个令牌,如果操作已经完成,您可以在其中轮询服务。

The background service could be a BackGroundWorker in Net Core.后台服务可以是 Net Core 中的 BackGroundWorker。 This is easy but not really fault tolerant, so some sort of db and retry logic could be good.这很容易,但不是真正的容错,所以某种数据库和重试逻辑可能是好的。

If you are in an intranet, you could also move to an inherently asynchronous protocol like RabbitMQ, where you send a StartOperation Message and then receive a Started Message when the process has completed.如果您在 Intranet 中,您还可以转而使用固有的异步协议,例如 RabbitMQ,您可以在其中发送 StartOperation 消息,然后在流程完成时接收 Started 消息。

Following is my understanding of the issue that you have posted:以下是我对您发布的问题的理解:

  1. You want to initiate a long running call, via Rest api call您想通过 Rest api 调用发起长时间运行的调用
  2. You want to use the Async call, but not sure how to maintain the DB context for a long running call which is used for db communication on regular basis during the operation您想使用异步调用,但不确定如何为长时间运行的调用维护数据库上下文,该调用在操作期间定期用于数据库通信

Couple of important points:几个要点:

  1. Mostly you are not clear regarding working of the Async calls大多数情况下,您不清楚异步调用的工作
  2. When you make an Async call, then it stores the current thread synchronization context for the continuity using state machine, it doesn't block any thread pool thread, it utilize the hardware based concurrency当您进行 Async 调用时,它使用状态机存储当前线程同步上下文以保持连续性,它不会阻塞任何线程池线程,它利用基于硬件的并发性
  3. Can use ConfigureAwait(false) on backend to avoid explicit reentry in the current synchronization context, which is better for performance可以在后端使用ConfigureAwait(false)避免在当前同步上下文中显式重入,这对性能更好
  4. Only challenge with Async calls to be genuine async the complete chain need to be Async enabled from the entry point, else the benefits can't be reaped, if you use Task.Wait or Task.Result anywhere, infact may even cause a deadlock in the ASP.Net只有通过 Async 调用才能挑战真正的 async 整个链需要从入口点启用 Async,否则无法获得好处,如果您在任何地方使用Task.WaitTask.ResultTask.Result甚至可能导致死锁ASP.NET

Regarding the long running operation, following are the options关于长时间运行的操作,以下是选项

  1. A Simple async call as suggested above, though it can help you can make large number of async calls (thus scalability) but context will be lost if the client goes away and no way to reap the status of operation back上面建议的简单异步调用,虽然它可以帮助您进行大量异步调用(因此可扩展性),但是如果客户端消失并且无法恢复操作状态,上下文将丢失
  2. You can make a fire and forget call, and use a mechanism like ASP.Net SignalR, which is like IObservable over the network and can help in notifying the client when the processing finish您可以进行即发即弃调用,并使用像 ASP.Net SignalR 这样的机制,它类似于网络上的IObservable ,可以帮助在处理完成时通知客户端
  3. Best option would be using a messaging queue like Rabbit MQ, which doesn't run the risk of client going down, it acts a producer consumer and can notify when the client comes up, in this case MQ can be notified when the process finish and thus client can be informed.最好的选择是使用像 Rabbit MQ 这样的消息队列,它不会冒客户端宕机的风险,它充当生产者消费者并且可以在客户端出现时通知,在这种情况下,MQ 可以在进程完成时得到通知,并且因此可以通知客户。 MQ can be used for both incoming and response message in an async manner MQ 可以异步方式用于传入和响应消息

In case, where client wants to periodically come up and check the status of the request, then DB persistence is important, which can be updated at regular intervals and it can be checked what's the status of the long running process.如果客户端想要定期上来并检查请求的状态,那么数据库持久性很重要,它可以定期更新,并且可以检查长时间运行的进程的状态。

Another option would be to use Hangfire.另一种选择是使用 Hangfire。 It will allow you to Enqueue the work that you want to execute to a persistent store eg SQL Server, MSMQ, Redis depending on what you have in your infrastructure.它将允许您将要执行的工作排入持久存储,例如 SQL Server、MSMQ、Redis,具体取决于您的基础架构中的内容。 The job will then be picked up by a worker which can also run in the ASP.NET process or a windows service.然后,该作业将由一个也可以在 ASP.NET 进程或 Windows 服务中运行的工作人员接手。 It's distributed too so you can have a number of instances of the workers running.它也是分布式的,因此您可以运行多个工作程序实例。 Also supports retrying failed jobs and has a dashboard to view the jobs.还支持重试失败的作业,并有一个仪表板来查看作业。 Best of all, it's free!最棒的是,它是免费的!

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

https://www.hangfire.io/ https://www.hangfire.io/

My question to you is now, are we tackling this the wrong way?我现在要问你的问题是,我们是否以错误的方式解决这个问题? I imagine it's bad practice to start these long running processes in the asp.net controller.我想在 asp.net 控制器中启动这些长时间运行的进程是不好的做法。

Generally, yes.一般来说,是的。 Ideally an ASP.NET service does not have any long-running processes inside it - or at the very least, no long-running processes that can't be easily and quickly shut down.理想情况下,ASP.NET 服务内部没有任何长时间运行的进程 - 或者至少,没有无法轻松快速关闭的长时间运行进程。

Doing work outside of an HTTP request (ie, request-extrinsic code) is most reliably achieved by adding a durable queue with a separate background processor .通过添加具有单独后台处理器的持久队列,最可靠地实现 HTTP 请求之外的工作(即请求外部代码)。 The "background processor" can be a Win32 service, possibly even on the same machine. “后台处理器”可以是 Win32 服务,甚至可能在同一台机器上。 With this architecture, the HTTP request places a request on the queue, and the processor picks up requests from the queue and executes them.在这种架构下,HTTP 请求将请求放入队列,处理器从队列中选取请求并执行它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM