如何从客户端取消异步任务

How to cancel async Task from the client

我在 ASP.Net C# web API 上有一个导入端点。 Javascript 客户端将项目列表发送到此 API 并且 API 在另一个线程(长任务)中处理此列表并立即 returns 进程的唯一 ID (GUID)。现在我需要从 CLIENT 取消后台任务。有可能以某种方式从客户端发送取消令牌吗?我试图将 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);

        }
    }

在较高级别上,您可以做的是在对工作进行排队时将进程 ID 与取消令牌源一起存储。然后你可以公开一个接受进程 ID 的新端点,从商店获取取消令牌源并取消关联的令牌:

        [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(); 以在请求取消时停止。

我添加了一个新端点,允许您取消挂起的操作。

免责声明
您肯定需要考虑一些事情,尤其是当它是 public api 时。您可以扩展商店以接受某种安全令牌,并在请求取消时检查它是否与排队工作的安全令牌相匹配。我的回答主要集中在问题的基础上

此外,我将商店的实现留给您自己想象;-)