繁体   English   中英

拒绝取消的任务如何取消和释放资源

[英]How to cancel and release resources of task refusing to cancel

我有一个在队列中执行“长时间运行”任务的网络服务,有时由于错误或验证不足(任务太大)而卡住。 我需要及时取消这些任务,以便下一个客户请求可以开始。

我目前使用CancellationToken超时 + 手动取消这些任务,并且我的代码中到处都是ThrowIfCancellationRequested 有时代码会卡在收到不合理请求的某个第 3 方 function 中,有时只是我的代码中的一个错误导致取消不会发生。

我已经阅读了很多关于使用BackgroundService, IHostedService和大量文章,这些文章展示了取消异步不可取消任务的不同方法,但它们似乎只是从任务中“返回”,让它继续运行。 这对我不起作用,因为单个请求在我的小型服务器上最多可能占用 90% 的 RAM 和 50% 的 CPU,并且可能永远不会自行取消。 所以这些解决方案会很快导致资源匮乏。

本文指出您不能取消不可取消的任务。 https://devblogs.microsoft.com/pfxteam/how-do-i-cancel-non-cancelable-async-operations/

编辑澄清:
我目前的解决方案是尊重CancellationToken ,它可以工作 99% 的时间。 失败的是这样的情况:

CT.ThrowIfCancellationRequested();
// The matrix Auu can become unreasonably large --> This 3rd party function takes minutes
var cholesky = SparseCholesky.Create(Auu, CSparse.ColumnOrdering.MinimumDegreeAtPlusA);

CT.ThrowIfCancellationRequested();

虽然我尝试修复这样的情况并在 function 调用之前抛出异常,但我无法全部找到它们,我宁愿让我的客户收到错误而不是让服务器长时间卡住。我也分叉了一些 3rd 方库以增强对CancellationToken的支持,但同样,有些库总是会让我感到惊讶。 我需要的是一个故障保护,以确保 web 服务不会卡住并变得不可用。

我目前使用的系统看起来像这样简化:

// this code is in a singleton service in an ASP.NET core 3.0 web app
// this one is used to manually cancel from another method if requested
private CancellationTokenSource cancelSource;
public async Task Advance(...)
{
   //...
   cancelSource = new CancellationTokenSource())

   ComputeActive(); // This is not awaited, which lets the request finnish (what Chris Pratt mentioned in his answer)

}
private async Task ComputeActive()
{
    //...
    // this combined token handles automatic timeout ~90sec
    // but it will not help if the code is stuck in something that doesn't have CancellationTokens
    using (var timeoutSource = new CancellationTokenSource(Active.ComputeTimeLimit))
    using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, cancelSource.Token))
    {
        try
        {
            // this is the "long-running" task (0.1seconds to 40 seconds usually)
            var file = await Task.Run(() => product.Create(Active.Action, linkedSource.Token), linkedSource.Token);;
        }catch(...)
    }
}

那么我的解决方案是什么? Thread.Abort() 还是重新启动整个应用程序更好?

解决方案:我通过按照答案中给出的建议将任务移动到另一个进程解决了这个问题,然后当使用CancellationToken需要太长时间时,我可以使用Environment.Exit(0) 然后必须重新启动工作进程。

任何时候你有一个长时间运行的任务,你首先应该让它脱离进程。 这意味着安排它通过另一个进程运行。 例如,您可以创建一个工作服务并通过某种事件通信模式远程排队工作,让它从数据库表中获取任务等。重要的事情是将其从您的 web 进程中取出,所以它不会不会影响您的应用程序或其线程池。

一个更简单但不那么强大的解决方案是使用在应用程序本身中运行的托管服务。 这至少提供了某种程度的隔离并且不会阻塞请求,但它仍然在同一个进程中,所以它使用同一个线程池,memory 等。

不想做的是在请求的上下文中运行任务,并且您绝对不想在不等待它的情况下这样做,我认为这可能是您的问题所在。 换句话说,你正在做类似的事情:

Task.Run(x => MyLongRunningMethod());

这让请求继续并完成,但是您已经创建了一个您不再有任何直接控制权的新线程。 如果它最终完成,那没什么大不了的,但如果它挂起,那么你已经永久地消耗了池中的一个线程,以及该线程所持有的任何资源。 那时你唯一能做的就是重新启动整个进程,因为没有办法再进入这个线程来杀死它。

取消令牌可以提供帮助,但它们并不神奇。 它们表明已请求取消,但一路下来的所有内容都必须支持取消。 如果您要调用的东西要么不支持传递取消令牌,不支持在某些子流程中取消,或者您甚至没有首先传递令牌,那么这一切都是为了没有。 这项工作将无限期地继续下去,直到它完成或出错。

总而言之,不要使用Task.Run除非您有取消任务的方法,它总是会完成,或者您实际上正在等待它。 即使那样,您也不应该在web应用程序中使用它,因为在最好的情况下,您只是将一个线程换成另一个,而在最坏的情况下,您会长时间使用池中的线程时间,减少您的 web 应用程序的潜在吞吐量。

将工作移出请求管道,理想情况下将其完全移出流程。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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