多线程环境中的任务列表 - “枚举操作可能无法执行”

时间:2012-08-05 18:56:03

标签: c# multithreading task-parallel-library

我有一个任务工厂开始执行许多任务,有时超过1000.我将每个任务添加到列表中,并在任务完成后将其删除。

var scan = Task.Factory.StartNew(() =>
            {
                return operation.Run();
            }, token.Token
            );

            operations.Add(scan);

任务完成时:

var finishedTask = scan.ContinueWith(resultTask =>
                OperationComplete(resultTask),
                TaskContinuationOptions.OnlyOnRanToCompletion
                );

public virtual void OperationComplete(Task task)
    {
        operations.Remove(task);
    }

一切都完成后:

    Task.Factory.ContinueWhenAll(operations.ToArray(),
         result =>
         {
             AllOperationsComplete();
         }, TaskContinuationOptions.None);

然后,在我的应用程序的某些点,我想得到运行任务的计数。 (这是我收到错误的地方:“收集被修改;枚举操作可能无法执行。”)

    public int Count()
    {  
        int running = operations.Count<Task>((x) => x.Status == TaskStatus.Running);
        return running;
    }

几个问题:

1)我是否应该担心从列表中删除任务?该列表很容易在1000年代。

2)什么是让Count()安全的最佳方法?如果我没记错的话,创建一个新列表并向其添加operations仍会枚举该集合。

4 个答案:

答案 0 :(得分:4)

您需要锁定以确保一次只有一个线程访问列表(无论是在删除还是计数期间),或者您应该使用并发集合。不要忘记Count(Func<T, bool>)需要迭代集合才能执行计数 - 这就像使用foreach循环...而且你不能修改集合(一般情况下) '重复它。

我怀疑ConcurrentBag在这里是一个合适的选择 - 当你使用TPL时,大概你可以使用.NET 4并发集合......

答案 1 :(得分:1)

您需要确保在迭代时不修改集合。大多数收藏品都不支持。 lock可能就足够了。 但是,您可能希望重新审视设计。长时间锁定集合可能会导致您希望从异步任务中获得的性能提升。

答案 2 :(得分:0)

鉴于代码已经将状态作为计数调用的一部分进行检查,并假设在所有任务都在集合中之前你没有进行计数,只是不删除它们似乎是最简单的答案。如果您决定将List替换为其他内容,请确保实际测量性能差异,特别是如果Count调用完成的次数相对于集合的大小较低。 :)

答案 3 :(得分:0)

您可以使用ConcurrentDictionary 跟踪你的任务(Concurrentbags不允许你删除特定的项目)。

ConcurrentDictionary<Task, string> runningTasks = new ConcurrentDictionary<Task, string>();

Task task = Task.Factory.StartNew(() =>
    {
        // Do your stuff
    }).ContinueWith(processedTask => {
        var outString; // A string we don't care about
        runningTasks.TryRemove(processedTask, out outString);
    });

runningTasks.TryAdd(task, "Hello I'm a task");
// Add lots more tasks to runningTasks

while (runningTasks.Count > 0)
{
     Console.WriteLine("I'm still waiting...");
     Thread.Sleep(1000);
}

如果你想做一个合适的“WaitAll”(需要LINQ):

    try
    {
        Task[] keys = runningTasks.Keys.Select(x => x).ToArray();
        Task.WaitAll(keys);
    }
    catch { } // WaitAll will always throw an exception.

希望它有所帮助。