Перенаправление на действие после завершения очереди фоновых задач - PullRequest
0 голосов
/ 14 ноября 2018

Я работаю над базовым решением .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 голосов
/ 23 мая 2019

Если вы хотите, чтобы текущая страница взаимодействовала с долго выполняющейся задачей, вам не обязательно нужны служебные данные BackgroundService. Эта функция предназначена для случаев, когда нет страницы для взаимодействия.

Во-первых, сервер не может вызвать клиента, чтобы сказать ему, чтобы он перезагрузился. По крайней мере, без использования WebSockets, что определенно было бы излишним для этого. Вместо этого вы будете использовать Javascript (AJAX), чтобы выполнять фоновые вызовы для опроса состояния вашей задачи. Это общий шаблон, используемый любым сложным веб-приложением.

На сервере вы создадите обычный метод асинхронного действия, который занимает все время, необходимое для выполнения задачи.

Веб-страница (после загрузки) вызовет этот метод действия с использованием AJAX и проигнорирует ответ. Время ожидания этого вызова истечет, но это не проблема, вам не нужен ответ, и сервер продолжит обрабатывать действие, даже если соединение с сокетом было разорвано.

Веб-страница впоследствии начнет опросить (используя AJAX) другой метод действия, который сообщит вам, завершена ли задача или нет. Вам понадобится некоторое общее состояние на сервере, возможно, таблица базы данных, которая обновляется вашей фоновой задачей и т. Д. Этот метод должен всегда возвращаться очень быстро - все, что ему нужно сделать, это прочитать текущее состояние задачи и вернуть этот статус .

Веб-страница будет продолжать опрашивать этот метод до тех пор, пока ответ не изменится (например, с ЗАПУСКА на ЗАВЕРШЕНО). После изменения статуса вы можете перезагрузить страницу, используя Javascript или все, что вам нужно сделать в ответ на выполнение задачи.

Примечание. Здесь есть некоторые нюансы, например, стоимость удержания клиентских подключений, время ожидания которых вы ожидаете. Если вам не все равно, вы можете оптимизировать их, но в большинстве случаев это не будет проблемой, и это добавляет сложности.

...