简体   繁体   English

正确取消较长的“字符串”任务继续? 强制任务执行顺序和线程使用情况?

[英]Cancelling a long “string” of Task continuations properly? Forcing Task execution order and thread usage?

I have a class that strings together small chunks of IO work as Task continuations. 我有一堂课,将一小部分IO工作作为Task延续。 Each time a piece of work is received a new Task is created, it is then added as a continuation to the LastCreatedTask . 每次收到一件作品时,都会创建一个新的Task ,然后将其作为LastCreatedTask添加到LastCreatedTask I am trying to determine the proper way to cancel all of these Task properly? 我正在尝试确定适当的方式来正确取消所有这些Task

Here is my setup currently 这是我目前的设置

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

So in the function ScheduleChunk a "chunk" of IO work (and a timeout) are passed to CreateProcessTask which creates a Task that will do the actual processing of the IO work. 因此,在功能ScheduleChunk ,将IO工作的“块”(和超时)传递给CreateProcessTaskCreateProcessTask创建一个Task ,它将对IO工作进行实际处理。 A CancellationToken is passed to this Task which is made by chaining two CancellationTokenSources together. 通过将两个CancellationTokenSources链接在一起,将CancellationToken传递到此Task

The first is the "main" CancellationTokenSource ; 第一个是“主要” CancellationTokenSource I want to be able to simply call Cancel on this source to cancel all of the Task that are chained. 我希望能够简单地在此源上调用“ Cancel ”来取消所有链接的Task The second source is one that will automagically cancel after some given period of time (this stops long running/stalled IO chunks). 第二个来源是将在给定时间段后自动取消的来源(这将停止长时间运行/停滞的IO块)。

Finally once the constructed Task is returned to ScheduleChunk is it added as a continuation to the LastCreatedTask which is the last task the was added as a continuation. 最后,一旦将构造的Task返回给ScheduleChunk ,它将作为LastCreatedTask添加到LastCreatedTaskLastCreatedTask是作为LastCreatedTask添加的最后一个任务。 This in effect makes a chain of Task that run in order one after the other. 实际上,这使得Task链按顺序依次运行。

1. Is my method above the proper way to cancel the chain of Task ? 1. 我的方法是否是取消Task链的正确方法? By calling Cancel on the TaskQueueTokenSource ? 通过在TaskQueueTokenSource上调用Cancel

2. Is the use of the TaskContinuationOptions.ExecuteSynchronously along with continuations the proper way to ensure that these Task are executed in order one after the other? 2. 使用TaskContinuationOptions.ExecuteSynchronously和continuations一起使用是确保这些Task依次执行的正确方法吗?

3. Is there a way to specify that I would like the same thread from the underlying TPL ThreadPool to work on this chain of Task ? 3. 有没有一种方法可以指定我希望底层TPL ThreadPool的相同线程可以在Task链上Task

From what I can tell a new thread should not be created for each continuation point, though it is possible at some point that a new thread could pickup the chain. 据我所知,不应为每个连续点创建一个新线程,尽管在某些时候可能会有一个新线程拾取该链。

The existing code does not work at all: 现有代码根本不起作用:

LastCreatedTask.ContinueWith((t) => ioChunkProcessTask)

This continuation merely returns a task whose result will be set to a constant object almost immediately. 这种继续只是返回一个任务,该任务的结果将几乎立即设置为一个常数对象。 That's all it does. 这就是全部。

Really, this code is structured awkwardly. 确实,此代码的结构笨拙。 This is much better: 这样更好:

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

await is made for sequencing async actions. await异步动作的顺序。 await can replace ContinueWith most of the time. await大多数情况下, await可以替换ContinueWith

The cancellation looks good. 取消看起来不错。 Maybe it can be simplified a bit but it's working fine. 也许可以简化一下,但是效果很好。

Regarding 3, you should never need this. 关于3,您永远不需要它。 Thread affinity is a rare thing and to be avoided. 线程关联是一种罕见的事情,应该避免。 There is no way to do this exactly as asked. 无法完全按照要求执行此操作。 Please elaborate what you want to achieve. 请详细说明您要实现的目标。


If you insist on using Task.Run, here's a sketch: 如果您坚持使用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);
}

Use a shared cancellation token that you pass to each task instead of creating a unique one for each task. 使用传递给每个任务的共享取消令牌,而不是为每个任务创建唯一的令牌。 When you cancel that token, all tasks using that token will then know to stop processing. 当您取消该令牌时,所有使用该令牌的任务都将知道停止处理。

You edited after I answer, so I'll reply to your individual numbered questions: 您在我回答后进行了修改,因此我将回答您的编号问题:

1. Is my method above the proper way to cancel the chain of Task? 1. 我的方法是否是取消Task链的正确方法? By calling Cancel on the TaskQueueTokenSource? 通过在TaskQueueTokenSource上调用Cancel?

According to msdn , best practices is to create one token, then passing that same token to each task. 根据msdn的说法 ,最佳做法是创建一个令牌,然后将相同的令牌传递给每个任务。

2. Is the use of the TaskContinuationOptions.ExecuteSynchronously along with continuations the proper way to ensure that these Task are executed in order one after the other? 2. 使用TaskContinuationOptions.ExecuteSynchronously和continuations一起使用是确保这些Task依次执行的正确方法吗?

No, you Tasks are meant to be run in parallel, the best practices are instead to have the tasks that rely on ordering to be call each other down a chain, The first calling the second and so on. 不,您是指任务应并行运行,最佳实践是使任务依赖于顺序相互调用,而第一个调用第二个,依此类推。 Alternatively you wait until the first task is complete before you start the second task. 或者,您可以等到第一个任务完成后再开始第二个任务。

3. Is there a way to specify that I would like the same thread from the underlying TPL ThreadPool to work on this chain of Task? 3. 是否有一种方法可以指定我希望底层TPL ThreadPool中的相同线程可以在Task链上工作?

It is very unlikely that you you should be doing this, part of the purpose of the thread pool and the Task Asynchronous Programming (TAP) and TPL is to abstract the explicit threading away. 您极不可能执行此操作,这是线程池的一部分,而任务异步编程(TAP)和TPL的目的之一就是将显式线程抽象化。 You aren't guaranteed the thread that a task is ran on or even whether or not a new thread is generated for that task without a lot of work. 您不能保证可以在其上运行任务的线程,也不能保证是否无需大量工作即可为该任务生成新线程。

That said, if, for some reason you really do need to do this a custom task scheduler is the answer 就是说,如果由于某种原因您确实需要执行此操作, 那么定制任务调度程序就是答案

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

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