简体   繁体   English

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

[英]C# Parallel - Adding items to the collection being iterated over, or equivalent?

Right now, I've got a C# program that performs the following steps on a recurring basis: 现在,我有一个C#程序,它定期执行以下步骤:

  • Grab current list of tasks from the database 从数据库中获取当前任务列表
  • Using Parallel.ForEach(), do work for each task 使用Parallel.ForEach(),可以为每个任务工作

However, some of these tasks are very long-running. 但是,其中一些任务非常长。 This delays the processing of other pending tasks because we only look for new ones at the start of the program. 这延迟了其他待处理任务的处理,因为我们只在程序开始时查找新的任务。

Now, I know that modifying the collection being iterated over isn't possible (right?), but is there some equivalent functionality in the C# Parallel framework that would allow me to add work to the list while also processing items in the list? 现在,我知道修改正在迭代的集合是不可能的(对吧?),但是在C#Parallel框架中是否有一些等价的功能可以让我将工作添加到列表中,同时还处理列表中的项目?

Here is an example of an approach you could try. 以下是您可以尝试的方法示例。 I think you want to get away from Parallel.ForEach ing and do something with asynchronous programming instead because you need to retrieve results as they finish, rather than in discrete chunks that could conceivably contain both long running tasks and tasks that finish very quickly. 我认为你想摆脱Parallel.ForEach ing并用异步编程做一些事情,因为你需要在结束时检索结果,而不是在可以想象包含长时间运行的任务和非常快速完成的任务的离散块中。

This approach uses a simple sequential loop to retrieve results from a list of asynchronous tasks. 此方法使用简单的顺序循环从异步任务列表中检索结果。 In this case, you should be safe to use a simple non-thread safe mutable list because all of the mutation of the list happens sequentially in the same thread. 在这种情况下,您应该可以安全地使用简单的非线程安全可变列表,因为列表的所有突变都在同一个线程中顺序发生。

Note that this approach uses Task.WhenAny in a loop which isn't very efficient for large task lists and you should consider an alternative approach in that case. 请注意,此方法在循环中使用Task.WhenAny ,这对于大型任务列表不是非常有效,在这种情况下您应该考虑替代方法。 (See this blog: http://blogs.msdn.com/b/pfxteam/archive/2012/08/02/processing-tasks-as-they-complete.aspx ) (参见此博客: http//blogs.msdn.com/b/pfxteam/archive/2012/08/02/processing-tasks-as-they-complete.aspx

This example is based on: https://msdn.microsoft.com/en-GB/library/jj155756.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
    }
}

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 . 使用TPL Dataflow中的ActionBlock<T> 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() : 使用BlockingCollection<T> ,可以在使用它时使用它来修改项目,以及来自ParallelExtensionsExtras的GetConsumingParititioner()以使其与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)); 

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

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