TransWikia.com

How to cancel async Task from the client

Stack Overflow Asked by Jaroslav Maly on December 3, 2020

I have on ASP.Net C# web API with an endpoint for the import. Javascript client sends a list of items to this API and API process this list in another thread (long task) and immediately returns unique id (GUID) of process. Now I need the cancel the background task from the CLIENT. Is possible to somehow send the cancelation token from the client? I have tried to add CancellationToken as a parameter to my controller async action but I don’t know how to pass it from the client. For simplification, we can use as the client the Postman app.

Sample server-side

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

        }
    }

One Answer

On a high level what you could do is store the process id along with a cancellation token source when you queue the work. Then you can expose a new endpoint that accepts a process id, gets the cancellation token source from the store and cancels the associated token:

        [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
        }

In the above example I modified the ProcessItemList to accept a cancellation token and pass it to PerformBulkOperation, assuming that method has support for cancellation tokens. If not, you can manually call ThrowIfCancellationRequested(); on the cancellation token at certain points in the code to stop when cancellation is requested.

I've added a new endpoint that allows you to cancel a pending operation.

Disclaimer
There are for sure some things you need to think about, especially when it is a public api. You can extend the store to accepts some kind of security token and when cancellation is requested you check whether it matches with the security token that queued the work. My answer is focused on the basics of the question

Also, I left the implementation of the store to your own imagination ;-)

Correct answer by Peter Bons on December 3, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP