完成后台任务队列后重定向到操作 [英] Redirect to action after finishing background task queue

查看:54
本文介绍了完成后台任务队列后重定向到操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究.Net核心解决方案,该解决方案从另一个微服务中获取存储文件的备份,并且由于此过程花费的时间太长,我们决定在后台任务下构建此例程. https://docs. microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1 我已经通过使用像下面这样的Queued后台任务来实现了后台:

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

谢谢!

解决方案

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

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

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

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

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

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

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

I'm working on a .Net core solution that takes backup of storage files from another microservice and because this process takes too long time, we decided to build this routine under a background task.By following this link: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1 I have implemented the background by using Queued background tasks like the following :

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

}

and in the controller action method I did that:

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


    }

The logger information displays successfully All what I need is to find away to be able to reload the index controller after the background task is done successfully but for some reason I don't know it can't be redirected.

The Index action method is like that :

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

Thanks !

解决方案

If you want the current page to interact with a long running task, you don't necessarily need the overhead of BackgroundService. That feature is for cases where there is no page to interact with.

First, the server cannot call a client to tell it to reload. At least not without the use of WebSockets, which would definitely be overkill for this. Instead, you will use Javascript (AJAX) to make background calls to poll for the status of your task. This is a common pattern used by any complex web application.

On the server, you'll create a normal async action method that takes all the time it needs to complete the task.

The web page (after it has loaded) will call this action method using AJAX and will ignore the response. That call will eventually time out, but it's not a concern, you don't need the response and the server will continue processing the action even though the socket connection has terminated.

The web page will subsequently begin polling (using AJAX) a different action method which will tell you whether the task has completed or not. You'll need some shared state on the server, perhaps a database table that gets updated by your background task, etc. This method should always return very quickly - all it needs to do is read the present state of the task and return that status.

The web page will continue polling that method until the response changes (e.g. from RUNNING to COMPLETED.) Once the status changes, then you can reload the page using Javascript or whatever you need to do in response to the task completing.

Note: There are some nuances here, like the cost of holding client connections that you expect to time out. If you care you can optimize these away but in most cases it won't be an issue and it adds complexity.

这篇关于完成后台任务队列后重定向到操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆