Chaining Task.WhenAll()
本文关键字:WhenAll Task Chaining | 更新日期: 2023-09-27 18:03:29
我已经提出了一些代码链在一起多个调用Task.WhenAll()
。我认为这是可行的,但它看起来有点滑稽。其目的是在关闭服务之前允许所有Tasks
完成。伪代码省略了一些循环,方法声明等…
//initialize once on startup
Task _completion = Task.FromResult(0);
//Every minute a timer fires and we start some tasks
// and then chain them into the existing Task
var newTasks = Enumerable.Range(0, 10).Select(_ => Task.Factory.StartNew(() => {/* long running stuff here */})).ToArray();
_completion = Task.WhenAll(_completion, Task.WhenAll(newTasks));
//At some point a shutdown will be requested, and we can just wait for the one Task to complete
_completion.Wait();
这是一个坏主意吗?我最终是否会持有每个Task
的引用,因此它们永远不会被垃圾收集,或者导致一些内部数组变得巨大,或者其他一些可怕的事情?
对我来说,反复从Task.WhenAll()
中取出结果并将其反馈到Task.WhenAll()
中感觉有点奇怪。我看了一下Task.WhenAll()的源代码,没有看到任何迹象表明这可能是一个问题。但我肯定不是这方面的专家。
我是否最终会持有每个Task的引用,因此它们永远不会被垃圾收集
Task.WhenAll
在所有任务完成时释放所有任务的内存。这意味着任何给定的未完成任务都会导致内存被保留给同一"批处理"中的所有其他任务,每个批处理都"高于"它。如果您的批处理规模特别大,并且完成批处理所需的时间差异很大,那么这可能是一个问题。如果您的代码不是这种情况,那么您的代码应该没问题。
public class ActiveTaskTracker
{
private HashSet<Task> tasks = new HashSet<Task>();
public void Add(Task task)
{
if (!task.IsCompleted)//short circuit as an optimization
{
lock (tasks)
tasks.Add(task);
task.ContinueWith(t => { lock (tasks)tasks.Remove(task); });
}
}
public Task WaitAll()
{
lock (tasks)
return Task.WhenAll(tasks.ToArray());
}
}
我是否最终会持有每个Task的引用,因此它们永远不会被垃圾收集
视情况而定。
单个Task.WhenAll(X)
将在X
中的每个元素完成时释放对X
中所有任务的引用1。换句话说,如果您有Task.WhenAll(A, Task.WhenAll(B))
,那么即使A
没有完成,对B
的引用也不会在它完成后保持。因此,只要更深层次的任务继续完成,它们就应该继续被丢弃。
注意,如果你有一个单一的任务深深"卡住"(即永远不会完成)。你最终会得到一条无止境的链条。
你添加链的方式(例如chain = Task.WhenAll(chain, Task.WhenAll(newTasks))
)是为了缓解这个问题,因为内部的Task.WhenAll()
仍然可以释放任务,即使chain
本身被卡住和增长。
另一方面,Servy给出的答案中的代码没有这个问题。
1来自参考源(Task.cs):
private sealed class WhenAllPromise : Task<VoidTaskResult>, ITaskCompletionAction
{
public void Invoke(Task completedTask)
{
...
// Decrement the count, and only continue to complete the promise if we're the last one.
if (Interlocked.Decrement(ref m_count) == 0)
{
...
for (int i = 0; i < m_tasks.Length; i++)
{
...
// Regardless of completion state, if the task has its debug bit set, transfer it to the
// WhenAll task. We must do this before we complete the task.
if (task.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true);
else m_tasks[i] = null; // avoid holding onto tasks unnecessarily
}
}
}
}