链接Task.WhenAll()

Bri*_*chl 3 .net c# task-parallel-library async-await

我想出了一些链接多个调用的代码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();
Run Code Online (Sandbox Code Playgroud)

出于任何原因,这是一个坏主意吗?我最终会持有对每一个的引用,Task以便它们永远不会被垃圾收集,或者导致某些内部数组变得巨大,或者其他一些可怕的东西?

对我来说,反复取出结果Task.WhenAll()并将其反馈给我感觉有点奇怪Task.WhenAll().我看了一下Task.WhenAll()的源代码,我没有看到任何表明这可能是个问题的东西.但我当然不是这个话题的专家.

Ser*_*rvy 5

我最终会持有对每个任务的引用,因此他们永远不会收集垃圾

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());
    }
}
Run Code Online (Sandbox Code Playgroud)