【问题标题】:How to remove Task from collection after completed完成后如何从集合中删除任务
【发布时间】:2011-05-22 22:07:32
【问题描述】:

假设我有一个System.Threading.Tasks.Task 的集合:

HashSet<Task> myTasks = new HashSet<Task>();

...我会定期向集合中提供更多数据,因为我有更多需要处理的数据:

foreach (DataItem item in itemsToProcess)
    myTasks.Add(
        Task.Factory.StartNew(
            () => Process(item),
            cancellationToken,
            TaskCreationOptions.LongRunning,
            TaskScheduler.Default));    

由于Tasks 在完成后仍保持TaskStatus.RanToCompletion 状态而不是消失,因此它们将保留在集合中,直到被明确删除,并且集合将无限增长。 Tasks 需要被修剪以防止这种情况发生。

我研究过的一种方法是让Task 访问该集合并让它在最后自行删除。但我也在研究一种架构,在该架构中我必须删除我的组件尚未创建的任务。我的第一个想法是为每个任务的完成附加一个触发器或事件,如下所示:

foreach (Task task in createdSomewhereElse)
{
    lock (myTasks) myTasks.Add(task);
    task.WhenTaskIsCompleted += 
        (o, ea) => { lock(myTasks) myTasks.Remove(task); };
    task.Start();
}

...但是Task 没有这样的事件。有什么好方法可以完成我正在寻找的东西吗?像这样的:

【问题讨论】:

    标签: c# .net-4.0 task-parallel-library


    【解决方案1】:

    您当然可以在任务完成时附加一个触发器:Task.ContinueWith(及其通用等价物)。这对你来说可能已经足够了。

    可能还希望将ConcurrentDictionary 用作一种穷人的并发集合——这样您在访问集合时就不必锁定。迭代时只需使用Keys 属性,并使用您喜欢的任何值作为值。

    【讨论】:

    • 乔恩,你有没有特别想实现的ConcurrentHashSet?据我所知,BCL 中没有内置任何内容。
    • @LukeH:Doh - 我在考虑 ConcurrentDictionary。将编辑:)
    • 这似乎正是我想要做的。 :) 那么用task.ContinueWith((t) =&gt; { lock(myTasks) myTasks.Remove(t); }); 替换+= 语句是否正确?另外,对@LukeH 的评论+1;我在系统库中没有看到其中之一。
    • 啊,激动了一秒钟……我实际上在实际代码中使用 ConcurrentDictionary,但抽象为 HashSet 以降低问题的复杂性。
    • @Calvin:这对我来说是正确的,是的。 ContinueWith 是 C# 5 中异步支持的基础之一:)
    【解决方案2】:

    为什么需要将任务保存在集合中?

    为什么不使用基于 BlockingCollection 和 Parallel.ForEach 的解决方案

    var sources = new BlockingCollection<DataItem>();
    
    Task.Factory.StartNew(() => {
        Parallel.ForEach(sources.GetConsumingPartitioner(),
                         item => Process(item));
    });
    

    现在您可以将您的项目输入到阻塞集合中,它们将被自动处理。

    foreach (DataItem item in itemsToProcess)
        sources.Add(item);
    

    您可以使用sources.Countforeach (DataItem item in sources)查看未加工的商品。 (与您的解决方案的不同之处在于您看不到当前正在处理的项目)

    【讨论】:

    • 有趣的选择。我将任务保存在一个集合中,这样我就可以看到有多少仍在处理中,等到所有当前正在处理的任务完成(Task.WaitAll(myTasks.ToArray());),在现实生活中我使用 ConcurrentDictionary 来关联数据项目与正在处理它的任务。
    • @adrianm,是否需要等待取消特定任务?
    • @Calvin,没有内置任何东西。Parallel.ForEach 有一些重载,允许您取消剩余的任务。我会在 DataItem 中添加一个标志或取消标记,并在 Process() 中检查取消
    • 在 BlockingCollection 上使用 Parallel.ForEach 是危险的:blogs.msdn.com/b/pfxteam/archive/2010/04/06/9990420.aspx
    • @Ronnie:如果您阅读自己的链接文章,您会发现解决方案是使用.GetConsumingPartitioner 而不是.GetConsumingEnumerable。然后看我上面的代码。
    【解决方案3】:

    使用ContinueWith 设置从集合中移除任务的操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多