正确取消一长串任务延续?强制任务执行顺序和线程使用情况
本文关键字:任务 顺序 执行 线程 情况 用情 取消 延续 | 更新日期: 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工作实际处理的Task
。CancellationToken
传递到此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的部分目的是抽象出显式线程。 如果不进行大量工作,则无法保证运行任务的线程,甚至无法保证是否为该任务生成新线程。
也就是说,如果由于某种原因您确实需要这样做,那么自定义任务计划程序就是答案。