完成后台任务队列后重定向到操作

时间:2018-11-14 03:24:15

标签: c# asp.net-core asp.net-core-mvc asp.net-core-webapi background-task

我正在研究.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");
        }


    }

记录器信息成功显示enter image description here 我需要做的就是设法在后台任务成功完成后重新加载索引控制器,但是由于某种原因,我不知道它不能被重定向。

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);
}

谢谢!

1 个答案:

答案 0 :(得分:0)

如果您希望当前页面与长时间运行的任务进行交互,则不一定需要BackgroundService的开销。该功能适用​​于没有页面可交互的情况。

首先,服务器无法调用客户端以告知其重新加载。至少在没有使用WebSocket的情况下,这肯定是多余的。相反,您将使用Javascript(AJAX)进行后台调用以轮询任务状态。这是任何复杂的Web应用程序都使用的常见模式。

在服务器上,您将创建一个普通的异步操作方法,该方法将花费所有时间来完成任务。

网页(加载后)将使用AJAX调用此操作方法,并且将忽略响应。该调用最终将超时,但这不是问题,您不需要响应,即使套接字连接已终止,服务器也将继续处理操作。

随后,网页将开始轮询(使用AJAX)一种不同的操作方法,该方法将告诉您任务是否已完成。您将需要服务器上的某些共享状态,也许是由后台任务更新的数据库表,等等。此方法应始终非常快速地返回-它所需要做的就是读取任务的当前状态并返回该状态

网页将继续轮询该方法,直到响应更改(例如,从“运行”变为“完成”。)状态更改后,您就可以使用Javascript或响应任务完成而需要执行的任何操作来重新加载页面。 / p>

注意:这里有一些细微差别,例如您希望超时的保持客户端连接的成本。如果您愿意的话,可以优化这些方法,但是在大多数情况下,这将不是问题,并且会增加复杂性。