我正在研究.Net核心解决方案,该解决方案从另一个微服务中获取存储文件的备份,并且由于此过程花费的时间太长,我们决定在后台任务下构建此例程。 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1 我已经通过使用排队的后台任务来实现后台,如下所示:
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILoggerFactory loggerFactory)
{
TaskQueue = taskQueue;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(
CancellationToken cancellationToken)
{
_logger.LogInformation("Queued Hosted Service is starting.");
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
_logger.LogInformation("Queued Hosted Service is stopping.");
}
}
}
在控制器操作方法中,我做到了:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult TakeBackup()
{
// Process #1: update latest backup time in setting table.
var _setting = _settingService.FindByKey("BackupData");
var data = JsonConvert.DeserializeObject<BackUpData>(_setting.Value);
data.LatestBackupTime = DateTime.UtcNow;
_setting.Value = JsonConvert.SerializeObject(data);
_settingService.AddOrUpdate(_setting);
// Process #2: Begin a background service to excaute the backup task.
_queue.QueueBackgroundWorkItem(async token =>
{
// instead of this staff I will replace by the API I want to consume.
var guid = Guid.NewGuid().ToString();
for (int delayLoop = 0; delayLoop < 3; delayLoop++)
{
_logger.LogInformation(
$"Queued Background Task {guid} is running. {delayLoop}/3");
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
_logger.LogInformation(
$"Queued Background Task {guid} is complete. 3/3");
// Here I need to redirect to the index view after the task is finished (my issue) ..
RedirectToAction("Index",new {progress="Done"});
});
return RedirectToAction("Index");
}
}
记录器信息成功显示 我需要做的就是设法在后台任务成功完成后重新加载索引控制器,但是由于某种原因,我不知道它不能被重定向。
Index操作方法如下:
public async Task<IActionResult> Index()
{
var links = new List<LinkObject>();
var files = await _storageProvider.GetAllFiles(null, "backup");
foreach (var f in files)
{
var file = f;
if (f.Contains("/devstoreaccount1/"))
{
file = file.Replace("/devstoreaccount1/", "");
}
file = file.TrimStart('/');
links.Add(new LinkObject()
{
Method = "GET",
Href = await _storageProvider.GetSasUrl(file),
Rel = f
});
}
return View(links);
}
谢谢!
答案 0 :(得分:0)
如果您希望当前页面与长时间运行的任务进行交互,则不一定需要BackgroundService的开销。该功能适用于没有页面可交互的情况。
首先,服务器无法调用客户端以告知其重新加载。至少在没有使用WebSocket的情况下,这肯定是多余的。相反,您将使用Javascript(AJAX)进行后台调用以轮询任务状态。这是任何复杂的Web应用程序都使用的常见模式。
在服务器上,您将创建一个普通的异步操作方法,该方法将花费所有时间来完成任务。
网页(加载后)将使用AJAX调用此操作方法,并且将忽略响应。该调用最终将超时,但这不是问题,您不需要响应,即使套接字连接已终止,服务器也将继续处理操作。
随后,网页将开始轮询(使用AJAX)一种不同的操作方法,该方法将告诉您任务是否已完成。您将需要服务器上的某些共享状态,也许是由后台任务更新的数据库表,等等。此方法应始终非常快速地返回-它所需要做的就是读取任务的当前状态并返回该状态
网页将继续轮询该方法,直到响应更改(例如,从“运行”变为“完成”。)状态更改后,您就可以使用Javascript或响应任务完成而需要执行的任何操作来重新加载页面。 / p>
注意:这里有一些细微差别,例如您希望超时的保持客户端连接的成本。如果您愿意的话,可以优化这些方法,但是在大多数情况下,这将不是问题,并且会增加复杂性。