【问题标题】:Redirect to action after finishing background task queue完成后台任务队列后重定向到操作
【发布时间】:2018-11-14 03:24:14
【问题描述】:

我正在开发一个 .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");
        }


    }

logger信息显示成功 我所需要的只是在后台任务成功完成后能够重新加载索引控制器,但由于某种原因我不知道它不能被重定向。

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

谢谢!

【问题讨论】:

  • Webprogramming 和 Multitasking 不能很好地混合。页面生命周期要求页面尽快从内存中发送和删除。多任务处理会使它在内存中保存很长时间。 msdn.microsoft.com/en-us/library/ms178472.aspx您正在执行的任何多任务处理都应该是客户端,网页之外(并行运行的Web服务),或两者兼而有之。
  • 你的“微服务”后台任务在哪里执行?是红隼产生这个过程吗?您的应用程序是否在 IIS 上运行?这是 Azure 函数还是其他远程托管函数?
  • 我将通过 QueueBackgroundWorkItem 中的 http 客户端调用 API 或我需要的 API。我需要的是能够在队列完成后再次重新加载页面。显示后台进程已结束。
  • @JimYarbro ,我有一个名为 [Take Backup] 按钮的视图,通过单击它,将启动后台任务从外部微服务获取数据,一旦完成,我只需要更新用户备份任务已完成。我通过使用“进程正在进行中”的标签将其应用于视图语法,在后台任务完成后,我将使用新的查询字符串重新加载视图 return RedirectToAction("Index" ,新 {status="完成"});在剃须刀中,我将检测查询字符串并知道后台进程已完成。
  • @FreedomDeveloper 我相信你错过了我的问题。您的“外部微服务”在哪里运行?有关各种选项,请参阅我之前的问题。

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


【解决方案1】:

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

首先,服务器不能调用客户端来告诉它重新加载。至少在不使用 WebSockets 的情况下是这样,这肯定是矫枉过正的。相反,您将使用 Javascript (AJAX) 进行后台调用以轮询任务的状态。这是任何复杂的 Web 应用程序都使用的常见模式。

在服务器上,您将创建一个正常的异步操作方法,该方法需要完成任务所需的所有时间。

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

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

网页将继续轮询该方法,直到响应更改(例如从 RUNNING 到 COMPLETED)。一旦状态更改,您可以使用 Javascript 或您需要执行的任何操作重新加载页面以响应任务完成。

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

【讨论】:

    猜你喜欢
    • 2012-03-11
    • 2013-07-17
    • 1970-01-01
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    相关资源
    最近更新 更多