简体   繁体   中英

How to cancel async Task from the client

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

        }
    }

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM