简体   繁体   English

链接Task.WhenAll()

[英]Chaining Task.WhenAll()

I've come up with a bit of code that chains together multiple calls to Task.WhenAll() . 我想出了一些链接到Task.WhenAll()多个调用的代码。 I think this works, but it looks a little bit funny. 我认为这有效,但看起来有点滑稽。 The purpose is to allow all the Tasks to complete before shutting down a service. 目的是在关闭服务之前允许完成所有Tasks Pseudo-code eliding some looping, method declarations, etc... 伪代码省略了一些循环,方法声明等...

//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();

Is this a bad idea for any reason? 出于任何原因,这是一个坏主意吗? Am I going to end up holding a reference to every Task so they can never get garbage collected, or cause some internal array to become huge, or some other terrible thing? 我最终会持有对每个Task的引用,以便它们永远不会被垃圾收集,或者导致某些内部数组变得庞大,或者其他一些可怕的东西?

It feels a little weird to me to repeatedly take the result from Task.WhenAll() and feed it back into Task.WhenAll() . 重复从Task.WhenAll()获取结果并将其反馈给Task.WhenAll() ,这对我来说感觉Task.WhenAll() I have taken a look at the source code for Task.WhenAll() , and I don't see anything that indicates this could be a problem. 我看了一下Task.WhenAll()源代码 ,我没有看到任何表明这可能是个问题的东西。 But I'm certainly no expert on the topic. 但我当然不是这个主题的专家。

Am I going to end up holding a reference to every Task so they can never get garbage collected 我最终会持有对每个任务的引用,因此他们永远不会收集垃圾

Task.WhenAll frees up the memory for all of the tasks when all of them finish. Task.WhenAll在所有任务完成后释放所有任务的内存。 This means that any given uncompleted task results in the memory being held onto for all of the other tasks in the same "batch", an every batch "above" it. 这意味着任何给定的未完成任务都会导致内存被保留在同一“批处理”中的所有其他任务中,每个批处理“高于”它。 If your batch sizes are particularly large, and have pretty wide variance in how long they take to complete, that could be a problem. 如果您的批量大小特别大,并且完成所需的时间差异很大,则可能会出现问题。 If that's not the case for you, than your code should be fine. 如果情况并非如此,那么您的代码应该没问题。

Fortunately, this problem can be optimized rather easily. 幸运的是,这个问题可以很容易地优化。 You can use a class in which you add every active task to a set of tasks, and then remove every task when it finishes. 您可以使用将每个活动任务添加到一组任务的类,然后在完成任务时删除每个任务。 You can then easily wait on every currently active task. 然后,您可以轻松地等待每个当前活动的任务。 This ensures that completed tasks don't have reference to them held onto. 这可以确保已完成的任务不会引用它们。 Not only does this mean not holding onto older classes for longer than necessary, but it separates out the logic of "holding onto all active tasks" into one place, thus simplifying the logic in your main application. 这不仅意味着没有超过必要的时间来保留旧类,而且它将“保持所有活动任务”的逻辑分离到一个地方,从而简化了主应用程序中的逻辑。 The memory optimization aside, it may improve code clarity. 除了内存优化,它可以提高代码清晰度。

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

Am I going to end up holding a reference to every Task so they can never get garbage collected 我最终会持有对每个任务的引用,因此他们永远不会收集垃圾

It depends. 这取决于。

A single Task.WhenAll(X) will free up the reference to all tasks in X at the moment every element in X is complete 1 . Task.WhenAll(X)将腾出的参考所有任务X的时刻中的每个元素X完成1。 Put another way, if you had Task.WhenAll(A, Task.WhenAll(B)) , references to B will not be held after it's completed, even if A is not complete. 换句话说,如果你有Task.WhenAll(A, Task.WhenAll(B)) ,那么即使A没有完成,也不会在完成后保留对B引用。 Therefore, as long as tasks in deeper levels keep completing, they should continue to get dropped off. 因此,只要更深层次的任务继续完成,它们就应该继续下降。

Note however, if you had a single task way down deep get "stuck" (ie never completes). 但是请注意,如果你有一个单一的任务方式深陷得到“卡住”(即永远不会完成)。 You'll end up a with a chain the continues to grow endlessly. 你最终将拥有一条连锁店,并且不断发展壮大。

The way you're adding to the chain (eg chain = Task.WhenAll(chain, Task.WhenAll(newTasks)) ) goes to alleviate the problem, because the inner Task.WhenAll() can still free up tasks even if chain itself is stuck and growing. 你添加到链中的方式(例如chain = Task.WhenAll(chain, Task.WhenAll(newTasks)) )可以缓解问题,因为即使chain本身,内部Task.WhenAll()仍然可以释放任务被困和成长。

On the other hand, the code in the answer posted by Servy doesn't suffer this problem. 另一方面,Servy发布的答案中的代码没有遇到这个问题。

1 Sourced from reference source (Task.cs) : 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
            }
        }
    }
}

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

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