正确取消一长串任务延续?强制任务执行顺序和线程使用情况

本文关键字:任务 顺序 执行 线程 情况 用情 取消 延续 | 更新日期: 2023-09-27 18:34:11

我有一个类,它将IO工作的小块串在一起作为Task延续。每次收到一件作品时,都会创建一个新Task,然后将其作为延续添加到LastCreatedTask中。我正在尝试确定正确取消所有这些Task的正确方法?

这是我目前的设置

private Task LastCreatedTask { get; set; }
private CancellationTokenSource TaskQueueTokenSource { get; set; }
public void ScheduleChunk(IOWorkChunk inChunk, int procTimeout)
{
    Task ioChunkProcessTask = CreateProcessTask(inChunk, procTimeout);
    LastCreatedTask.ContinueWith(
        (t) => ioChunkProcessTask.Start(), 
        TaskContinuationOptions.ExecuteSynchronously);
    LastCreatedTask = ioChunkProcessTask;
}
private Task CreateProcessTask(IOWorkChunk inChunk, int procTimeout)
{
    // Create a TokenSource that will cancel after a given timeout period
    var ProcessTimeoutTokenSource = new CancellationTokenSource(
        TimeSpan.FromSeconds(procTimeout));
    // Create a TokenSource for the Task that is a 
    // link between the timeout and "main" token source
    var ProcessTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
        TaskQueueTokenSource.Token, 
        ProcessTimeoutTokenSource.Token);
    // Create a Task that will do the actual processing
    Task ioChunkProcessTask = new Task(() =>
    {
        if(!ProcessTokenSource.Token.IsCancellationRequested)
            inChunk.DoProcessing(ProcessTokenSource.Token);
    }, ProcessTokenSource.Token);
    return ioChunkProcessTask;
}

因此,在函数ScheduleChunk IO工作的"块"(和超时(被传递给CreateProcessTask这将创建一个将执行IO工作实际处理的TaskCancellationToken传递到此Task,该通过将两个CancellationTokenSources链接在一起而产生。

第一个是"主要"CancellationTokenSource;我希望能够简单地在此源上调用Cancel以取消所有链接的Task。第二个源是在给定时间段后自动取消的源(这会停止长时间运行/停止的 IO 块(。

最后,一旦构造Task返回到ScheduleChunk它就会作为延续添加到LastCreatedTask这是作为延续添加的最后一个任务。这实际上使一个接一个的Task链按顺序运行。

1.我的上述方法是否是取消Task链的正确方法?通过拨打Cancel TaskQueueTokenSource

2.使用TaskContinuationOptions.ExecuteSynchronously和延续是确保这些Task按顺序一个接一个地执行的正确方法吗?

3.有没有办法指定我希望来自底层 TPL ThreadPool的同一线程在这个Task链上工作?

据我所知,不应该为每个延续点创建一个新线程,尽管在某些时候新线程可能会拾取链条。

正确取消一长串任务延续?强制任务执行顺序和线程使用情况

现有代码根本不起作用:

LastCreatedTask.ContinueWith((t) => ioChunkProcessTask)

此延续仅返回一个任务,其结果将几乎立即设置为常量对象。这就是它所做的一切。

实际上,这段代码的结构很笨拙。这要好得多:

async Task RunProcessing() {
 while (...) {
  await CreateProcessTask(...);
 }
}

await用于对异步操作进行排序。 await大部分时间都可以替换ContinueWith

取消看起来不错。也许它可以简化一点,但它工作正常。

关于 3,你永远不需要这个。线程亲和力是一件罕见的事情,应该避免。没有办法完全按照要求做到这一点。请详细说明您想要实现的目标。


如果你坚持使用Task.Run,这里有一个草图:

Task CreateProcessTask(IOWorkChunk inChunk, int procTimeout)
{
    // Create a TokenSource that will cancel after a given timeout period
    var ProcessTimeoutTokenSource = new CancellationTokenSource(
        TimeSpan.FromSeconds(procTimeout));
    // Create a TokenSource for the Task that is a 
    // link between the timeout and "main" token source
    var ProcessTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
        TaskQueueTokenSource.Token, 
        ProcessTimeoutTokenSource.Token);
    return Task.Run(() => {
        inChunk.DoProcessing(ProcessTokenSource.Token);
    }, ProcessTokenSource.Token);
}

使用传递给每个任务的共享取消令牌,而不是为每个任务创建唯一的取消令牌。 取消该令牌时,使用该令牌的所有任务都将知道停止处理。

您在我回答后进行了编辑,因此我将回复您的个人编号问题:

1. 我的上述方法是否是取消任务链的正确方法?通过在 TaskQueueTokenSource 上调用 Cancel?

根据 msdn,最佳做法是创建一个令牌,然后将相同的令牌传递给每个任务。

2. 使用TaskContinuationOptions.ExecuteSyncly和延续是确保这些任务按顺序一个接一个地执行的正确方法吗?

不,您的任务是并行运行的,最佳做法是让依赖排序的任务在链中相互调用,第一个调用第二个,依此类推。 或者,您可以等到第一个任务完成,然后再开始第二个任务。

3. 有没有办法指定我希望来自底层 TPL 线程池的同一线程在此任务链上工作?

你不太可能这样做,线程池和任务异步编程(TAP(和TPL的部分目的是抽象出显式线程。 如果不进行大量工作,则无法保证运行任务的线程,甚至无法保证是否为该任务生成新线程。

也就是说,如果由于某种原因您确实需要这样做,那么自定义任务计划程序就是答案。