C#Parallel - 将项目添加到正在迭代的集合中,还是等效的?

时间:2015-11-11 20:37:34

标签: c# parallel-processing parallel.foreach

现在,我已经有了一个C#程序,它会定期执行以下步骤:

  • 从数据库中获取当前任务列表
  • 使用Parallel.ForEach(),为每项任务工作

但是,其中一些任务的运行时间非常长。这延迟了其他待处理任务的处理,因为我们只在程序开始时查找新的任务。

现在,我知道修改正在迭代的集合是不可能的(对吧?),但是在C#Parallel框架中有一些等价的功能可以让我在列表中添加工作同时处理项目在列表中?

2 个答案:

答案 0 :(得分:2)

以下是您可以尝试的方法示例。我想你想要远离Parallel.ForEach并使用异步编程做一些事情,因为你需要在结束时检索结果,而不是在可以想象包含长时间运行的任务和快速完成的任务的离散块中

此方法使用简单的顺序循环从异步任务列表中检索结果。在这种情况下,您应该可以安全地使用简单的非线程安全可变列表,因为列表的所有变异都在同一个线程中顺序发生。

请注意,此方法在循环中使用Task.WhenAny,这对于大型任务列表来说效率不高,在这种情况下您应该考虑另一种方法。 (参见此博客:http://blogs.msdn.com/b/pfxteam/archive/2012/08/02/processing-tasks-as-they-complete.aspx

此示例基于:https://msdn.microsoft.com/en-GB/library/jj155756.aspx

private async Task<ProcessResult> processTask(ProcessTask task) 
{
    // do something intensive with data
}

private IEnumerable<ProcessTask> GetOutstandingTasks() 
{
    // retreive some tasks from db
}

private void ProcessAllData()
{
    List<Task<ProcessResult>> taskQueue = 
        GetOutstandingTasks()
        .Select(tsk => processTask(tsk))
        .ToList(); // grab initial task queue

    while(taskQueue.Any()) // iterate while tasks need completing
    {
        Task<ProcessResult> firstFinishedTask = await Task.WhenAny(taskQueue); // get first to finish
        taskQueue.Remove(firstFinishedTask); // remove the one that finished
        ProcessResult result = await firstFinishedTask; // get the result
        // do something with task result
        taskQueue.AddRange(GetOutstandingTasks().Select(tsk => processData(tsk))) // add more tasks that need performing
    }
}

答案 1 :(得分:1)

Generally speaking, you're right that modifying a collection while iterating it is not allowed. But there are other approaches you could be using:

  • Use ActionBlock<T> from TPL Dataflow. The code could look something like:

    var actionBlock = new ActionBlock<MyTask>(
        task => DoWorkForTask(task),
        new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded });
    
    while (true)
    {
        var tasks = GrabCurrentListOfTasks();
        foreach (var task in tasks)
        {
            actionBlock.Post(task);
    
            await Task.Delay(someShortDelay);
            // or use Thread.Sleep() if you don't want to use async
        }
    }
    
  • Use BlockingCollection<T>, which can be modified while consuming items from it, along with GetConsumingParititioner() from ParallelExtensionsExtras to make it work with Parallel.ForEach():

    var collection = new BlockingCollection<MyTask>();
    
    Task.Run(async () =>
    {
        while (true)
        {
            var tasks = GrabCurrentListOfTasks();
            foreach (var task in tasks)
            {
                collection.Add(task);
    
                await Task.Delay(someShortDelay);
            }
        }
    });
    
    Parallel.ForEach(collection.GetConsumingPartitioner(), task => DoWorkForTask(task));