对于我的ASP.NET核心MVC应用程序,我需要一个处理数据的控制器操作。这个过程需要一些时间,所以我不想阻塞请求。在控制器操作中,我宁愿启动一个后台工作进程,并立即结束请求,告诉用户处理正在进行中。然后使用第二个控制器动作来访问处理后的数据。
在后台工作程序中,我需要访问DbContext以将处理后的数据存储在我的数据库中。(或通过依赖注入注入的任何其他服务。)我发现通过IServiceScopeFactory创建一个新的独立于请求的作用域是可行的,这反过来又给了我一个ServiceProvider
public class ProcessingController : Controller
{
private readonly IServiceScopeFactory mServiceProvider;
public HomeController(IServiceScopeFactory serviceProvider)
{
mServiceProvider = serviceProvider;
}
public IActionResult BeginProcessing(int id)
{
var longRunningScope = mServiceProvider.CreateScope();
var _ = Task.Run(() => {
try {
var context = longRunningScope.ServiceProvider.GetRequiredService<DbContext>();
var workItem = context.Items.First(i => i.Id == id)
...
}
finally {
longRunningScope.Dispose();
}
});
return Ok();
}
}有没有更好的(更像ASP.NET-Core风格的)方法来做到这一点?请注意,我的“长时间运行”操作只需要2-5秒,并且需要同时处理多个用户。不需要按顺序处理请求的后台线程。
发布于 2020-12-23 14:57:50
我认为hosted services不能很好地满足我的需求,因为它需要显式地实现和注入,这使得向它传递数据变得很困难。
这是一个更灵活、更易于使用的解决方案ScopedBackgroundTaskRunner,它在侦听shutdown事件的同时,在自己的任务和作用域中运行操作。该操作接收相应的取消令牌以及作用域ServiceProvider,以获得任何所需的服务。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace WebApplication2.Support
{
/// <summary>
/// Service class that executes tasks which run in their own thread with their own scope
/// and can thus continue executing after the web request ended.
/// </summary>
/// <remarks>
/// Register via:
/// services.AddTransient<ScopedBackgroundTaskRunner>();
/// </remarks>
public class ScopedBackgroundTaskRunner
{
private readonly ILogger<ScopedBackgroundTaskRunner> mLogger;
private CancellationTokenSource mStoppingCts;
private IServiceProvider mServiceProvider;
public ScopedBackgroundTaskRunner(IServiceProvider services,
ILogger<ScopedBackgroundTaskRunner> logger,
IHostApplicationLifetime lifetime)
{
mServiceProvider = services;
mLogger = logger;
lifetime.ApplicationStopping.Register(OnAppStopping);
}
private void OnAppStopping()
{
if (mStoppingCts != null)
{
mLogger.LogDebug($"Cancel due to app shutdown");
mStoppingCts.Cancel();
}
}
public void Execute(Action<IServiceProvider, CancellationToken> action, CancellationToken stoppingToken) {
Execute(action, "<unnamed>", stoppingToken);
}
public void Execute(Action<IServiceProvider, CancellationToken> action, string actionName, CancellationToken stoppingToken)
{
mStoppingCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
var scope = mServiceProvider.CreateScope();
var _ = Task.Run(() => {
mLogger.LogTrace($"Executing action '{actionName}' on thread {Thread.CurrentThread.ManagedThreadId}...");
try
{
action.Invoke(scope.ServiceProvider, mStoppingCts.Token);
}
finally {
mLogger.LogTrace($"Action '{actionName}' {(mStoppingCts.IsCancellationRequested ? "canceled" : "finished")}" +
$" on thread {Thread.CurrentThread.ManagedThreadId}");
scope.Dispose();
var mStoppingCtsCopy = mStoppingCts;
mStoppingCts = null;
mStoppingCtsCopy.Dispose();
}
}, mStoppingCts.Token);
}
}
}发布于 2020-12-22 09:18:05
考虑将您的后台任务实现为hosted service。
使用这种方法,后台服务的生命周期是受管理的(如果宿主环境正在关闭,运行时将通过CancellationToken请求取消,而在现有代码中,您的任务不会被礼貌地停止)。
https://stackoverflow.com/questions/65401889
复制相似问题