Как отменить асинхронную задачу с клиента

У меня есть веб-API ASP.Net С# с конечной точкой для импорта. Клиент Javascript отправляет список элементов в этот API, и API обрабатывает этот список в другом потоке (длинная задача) и немедленно возвращает уникальный идентификатор (GUID) процесса. Теперь мне нужно отменить фоновую задачу из КЛИЕНТА. Можно ли как-то отправить токен отмены с клиента? Я попытался добавить CancellationToken в качестве параметра в асинхронное действие моего контроллера, но я не знаю, как передать его от клиента. Для упрощения мы можем использовать в качестве клиента приложение Postman.

Пример на стороне сервера

    [HttpPost]
    [UserContextActionFilter]
    [RequestBodyType(typeof(List<List<Item>>))]
    [Route("api/bulk/ImportAsync")]
    public async Task<IHttpActionResult> ImportAsync()
    {
        var body = await RequestHelper.GetRequestBody(this);
        var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
        var resultWrapper = new AsynckResultWrapper(queue.Count);


        HostingEnvironment.QueueBackgroundWorkItem(async ct =>
        {
            foreach (var item in queue)
            {
                var result = await ProcessItemList(item, false);
                resultWrapper.AddResultItem(result);
            }
        });

        return Ok(new
        {
            ProcessId = resultWrapper.ProcessId.ToString()
        });
    }


    private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, bool runInOneTransaction = false)
    {
        try
        {
            var result = await PerformBulkOperation(true, itemList);
            return new ResultWrapper(result);
        }
        catch (Exception ex)
        {

            // process exception
            return new ResultWrapper(ex);

        }
    }

person Jaroslav Maly    schedule 13.11.2020    source источник


Ответы (1)


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

        [HttpPost]
        [UserContextActionFilter]
        [RequestBodyType(typeof(List<List<Item>>))]
        [Route("api/bulk/ImportAsync")]
        public async Task<IHttpActionResult> ImportAsync()
        {
            var body = await RequestHelper.GetRequestBody(this);
            var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
            var resultWrapper = new AsynckResultWrapper(queue.Count);

            HostingEnvironment.QueueBackgroundWorkItem(async ct =>
            {
                var lts = CancellationTokenSource.CreateLinkedTokenSource(ct);
                var ct = lts.Token;
                TokenStore.Store(resultWrapper.ProcessId, lts);

                foreach (var item in queue)
                {
                    var result = await ProcessItemList(item, ct, false);
                    resultWrapper.AddResultItem(result);
                }

                TokenStore.Remove(processId) // remove the cancellation token source from storage when doen, because there is nothing to cancel
            });

            return Ok(new
            {
                ProcessId = resultWrapper.ProcessId.ToString()
            });
        }


        private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, CancellationToken token, bool runInOneTransaction = false)
        {
            try
            {
                var result = await PerformBulkOperation(true, itemList, token);
                return new ResultWrapper(result);
            }
            catch (Exception ex)
            {

                // process exception
                return new ResultWrapper(ex);

            }
        }

        [Route("api/bulk/CancelImportAsync")]
        public async Task<IHttpActionResult> CancelImportAsync(Guid processId)
        {
            var tokenSource = TokenStore.Get(processId);
            tokenSource.Cancel();

            TokenStore.Remove(processId) // remove the cancellation token source from storage when cancelled
        }

В приведенном выше примере я изменил ProcessItemList, чтобы принять токен отмены и передать его PerformBulkOperation, предполагая, что этот метод поддерживает токены отмены. Если нет, вы можете вручную вызвать ThrowIfCancellationRequested(); для маркера отмены в определенных точках кода, чтобы остановить, когда запрашивается отмена.

Я добавил новую конечную точку, которая позволяет отменить незавершенную операцию.

Отказ от ответственности
Наверняка есть некоторые вещи, о которых вам нужно подумать, особенно когда это общедоступный API. Вы можете расширить хранилище, чтобы оно принимало какой-либо токен безопасности, и когда запрашивается отмена, вы проверяете, совпадает ли он с токеном безопасности, который поставил работу в очередь. Мой ответ сосредоточен на основах вопроса

Также я оставил реализацию магазина на ваше воображение ;-)

person Peter Bons    schedule 16.11.2020
comment
Я не могу сказать, является ли это безопасным решением - т.е. может ли процесс быть остановлен произвольным клиентом? т.е. клиент A вызывает действие импорта, а затем какой-то случайный клиент (другой rom A) вызывает CancelImportAsync? Таким образом, клиент А никогда не получит свои результаты, даже если он не отменил импорт. - person Michał Turczyn; 16.11.2020
comment
@MichałTurczyn Да, наверняка есть некоторые вещи, о которых вам нужно подумать, особенно когда это общедоступный API. Вы можете расширить хранилище, чтобы оно принимало какой-либо токен безопасности, и когда запрашивается отмена, вы проверяете, совпадает ли он с токеном безопасности, который поставил работу в очередь. Мой ответ сосредоточен на основах вопроса. - person Peter Bons; 16.11.2020
comment
@MichałTurczyn Ответ на переполнение стека не несет ответственности за предоставление полного решения, учитывающего все возможные перестановки. Человек, использующий ответ, несет ответственность за использование своего мозга и изменение ответа в соответствии со своим конкретным сценарием. Если они этого не делают, это их вина, когда возникают проблемы. - person Ian Kemp; 16.11.2020
comment
@PeterBons Большое спасибо. Это то, что мне нужно. - person Jaroslav Maly; 18.11.2020