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

Posted

技术标签:

【中文标题】如何从客户端取消异步任务【英文标题】:How to cancel async Task from the client 【发布时间】:2021-02-25 07:39:28 【问题描述】:

我在 ASP.Net C# Web API 上有一个用于导入的端点。 javascript 客户端向这个 API 发送一个项目列表,API 在另一个线程(长任务)中处理这个列表,并立即返回进程的唯一 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);

        
    

【问题讨论】:

请阅读:How do I cancel an HTTP fetch() request? 和 Using CancellationTokens in ASP.NET Core MVC controllers 所以要明确一点,您要取消在后台工作人员排队的工作吗? @PeterBons 是的,我想取消后台工作人员的任务。有什么办法吗? 【参考方案1】:

在高层次上,您可以在排队工作时将进程 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();,以在请求取消时停止。

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

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

另外,我把 store 的实现留给你自己想象;-)

【讨论】:

我不知道这是否是安全的解决方案——即进程可以被任意客户端停止吗? IE。客户端 A 调用导入操作,然后某个随机客户端(其他 rom A)调用 CancelImportAsync?这样一来,即使客户 A 没有取消导入,他也永远不会得到他的结果。 @MichałTurczyn 是的,您肯定需要考虑一些事情,尤其是当它是公共 API 时。您可以扩展商店以接受某种安全令牌,并在请求取消时检查它是否与排队工作的安全令牌匹配。我的回答集中在问题的基础上。 @MichałTurczyn 提供一个考虑到所有可能排列的完整解决方案不是 Stack Overflow 答案的责任。使用答案的人有责任使用他们的大脑并改变答案以适应他们的具体情况。如果他们不这样做,那么问题出现时就是他们的错。 @PeterBons 非常感谢。这就是我需要的。

以上是关于如何从客户端取消异步任务的主要内容,如果未能解决你的问题,请参考以下文章

Android取消多次调用的异步任务

如何取消 useEffect 清理函数中的所有订阅和异步任务?

如何在 C# Winforms 应用程序中取消执行长时间运行的异步任务

async和await异步编程资源汇总

Android:取消异步任务

在 useEffect 挂钩中取消所有异步/等待任务以防止反应中的内存泄漏的正确方法是啥?