我想出了一些将多个调用链接到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()
对我来说感觉有点奇怪。我看了source code for Task.WhenAll(),我没有看到任何表明这可能是个问题的东西。但我当然不是这个主题的专家。
答案 0 :(得分:5)
当所有任务完成时,我最终会持有对每个任务的引用,以便它们永远不会被垃圾收集
Task.WhenAll
释放所有任务的内存。这意味着任何给定的未完成任务都会导致内存被保留在同一"批次"中的所有其他任务中,每个批次"以上"它。如果您的批量大小特别大,并且完成所需的时间差异很大,则可能会出现问题。如果情况并非如此,那么您的代码应该没问题。
幸运的是,这个问题可以很容易地优化。您可以使用将每个活动任务添加到一组任务的类,然后在完成任务时删除每个任务。然后,您可以轻松地等待每个当前活动的任务。这可以确保完成的任务不会引用它们。这不仅意味着不会持续超过必要的时间,而且会分离出“#34;保持所有活动任务的逻辑”。在一个地方,从而简化了主应用程序中的逻辑。除了内存优化,它可以提高代码清晰度。
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());
}
}
答案 1 :(得分:0)
我最终会持有对每个任务的引用,以便它们永远不会被垃圾收集
取决于。
当Task.WhenAll(X)
中的每个元素完成 1 时,单个X
将释放对X
中所有任务的引用。换句话说,如果您有Task.WhenAll(A, Task.WhenAll(B))
,则B
的引用在完成后将不会被保留,即使A
未完成。因此,只要更深层次的任务继续完成,它们就应该继续下降。
但是请注意,如果你有一个任务方式向下深陷“卡住”(即永远不会完成)。你最终将成为一个连锁店,并继续无休止地成长。
您添加到链中的方式(例如chain = Task.WhenAll(chain, Task.WhenAll(newTasks))
)可以缓解这个问题,因为即使Task.WhenAll()
本身被卡住,内部chain
仍然可以释放任务越来越大。
另一方面,Servy发布的答案中的代码没有遇到这个问题。
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
}
}
}
}