我正在使用C#和ASP.NETCore2.0开发一个带有RESTAPI的Web应用程序。
我想要实现的是,当客户端向端点发送请求时,我将运行一个与客户端请求上下文分离的后台任务,如果任务成功启动,则该任务将结束。
我知道有HostedService
,但问题是HostedService
在服务器启动时启动,据我所知,没有办法从控制器手动启动HostedService
。
下面是一个简单的代码来演示这个问题。
[Authorize(AuthenticationSchemes = "UsersScheme")]
public class UsersController : Controller
{
[HttpPost]
public async Task<JsonResult> StartJob([FromForm] string UserId, [FromServices] IBackgroundJobService backgroundService)
{
// check user account
(bool isStarted, string data) result = backgroundService.Start();
return JsonResult(result);
}
}
4条答案
按热度按时间pinkon5k1#
您仍然可以将
IHostedService
与BlockingCollection
结合使用,作为后台任务的基础。为
BlockingCollection
创建一个 Package ,这样我们就可以将其作为单例注入。当集合为空时,
BlockingCollection.Take
不会占用处理器时间。将取消令牌传递给.Take
方法将在令牌取消时正常退出。对于后台进程,我们可以使用
IHostedService
-Microsoft.Extensions.Hosting.BackgroundService
的“内置”实现。此服务将使用从“队列”中提取的任务。
然后,我们可以从控制器添加新任务,而无需等待它们完成
应将阻塞集合的 Package 器注册为单例依赖项注入
注册后台服务
gcuhipw92#
这在很大程度上是从skjagini's answer中链接的文档中得到的启发,并进行了一些改进。
我认为在这里重申整个示例可能会有所帮助,以防链接在某个点中断。最值得注意的是,我注入了一个
IServiceScopeFactory
,以允许后台进程自己安全地请求服务。核心思想是创建一个任务队列,用户可以将其注入到控制器中,然后向其分配任务。同一个任务队列存在于长时间运行的托管服务中,该服务一次将一个任务出列并执行它。
工作队列:
在任务队列的核心,我们有一个线程安全的
ConcurrentQueue<>
。因为我们不想在新任务可用之前轮询队列,所以我们使用SemaphoreSlim
对象来跟踪队列中的当前任务数。每次调用Release
时,内部计数器都会递增。WaitAsync
方法会一直阻塞,直到内部计数器大于0。并且随后将其递减。为了使任务出队和执行任务,我们创建了一个后台服务:
最后,我们需要使任务队列可用于依赖注入,并启动后台服务:
现在,我们可以将后台任务队列注入控制器并将任务入队:
为什么要使用
IServiceScopeFactory
?理论上,我们可以直接使用已经注入到控制器中的服务对象,这对于单例服务以及大多数作用域服务都很有效。
但是,对于实现
IDisposable
(例如DbContext
)的作用域服务,这可能会中断:将任务入队后,控制器方法返回,请求完成。然后框架清理注入的服务。如果我们的后台任务足够慢或延迟,它可能会尝试调用已释放服务的方法,然后会遇到错误。为了避免这种情况,我们的排队任务应该总是创建它们自己的服务作用域,并且不应该使用来自周围控制器的服务示例。
km0tfn4u3#
Microsoft已在https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1中记录了相同内容
它使用BackgroundTaskQueue完成,BackgroundTaskQueue获取从Controller分配的工作,而该工作由派生自BackgroundService的QueueHostedService执行。
umuewwlo4#
您可以在
ThreadPool
中使用另一个线程:将方法排入队列以供执行。当执行绪集区执行绪变成可用时,就会执行方法。