阻止收集过程一次n个项目 - 一旦完成,就会继续

时间:2014-08-29 02:52:05

标签: c# .net task-parallel-library async-await blockingcollection

我有以下情景。

  1. 我从数据库中取出50个作业到阻塞集合中。

  2. 每项工作都是一项长期工作。 (可能是)。所以我想在一个单独的线程中运行它们。 (我知道 - 最好将它们作为Task.WhenAll运行,然后让TPL弄明白 - 但我想同时控制多少次运行)

  3. 说我想同时运行其中的5个(可配置)

  4. 我创建了5个任务(TPL),每个作业一个并行并行运行。

  5. 我想要做的是,只要第4步中的一个作业完成,就立即在阻止集合中选择下一个作业,并继续完成所有50个作业。

    我正在考虑创建一个Static blockingCollection和一个TaskCompletionSource,它将在作业完成时调用,然后它可以再次调用使用者从队列中一次选择一个作业。我还想在每项工作上调用async / await - 但这是最​​重要的 - 不确定这是否会对该方法产生影响。

    这是完成我想要做的事情的正确方法吗?

    this链接类似,但是我想在前N个项目中的一个完成后立即处理下一个作业。毕竟不是N.

    更新:

    好的,如果有人想在以后使用它,我会让这段代码完全符合我的要求。如下所示,创建了5个线程,每个线程在完成当前操作时启动下一个作业。在任何给定时间只有5个线程处于活动状态。我知道这可能不会像这样100%工作,并且如果与一个cpu / core一起使用,将会出现上下文切换的性能问题。

    var block = new ActionBlock<Job>(
                    job => Handler.HandleJob(job), 
                        new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5 });
    
                  foreach (Job j in GetJobs())
                      block.SendAsync(j);
    
      

    Job 2在线程上开始:13。等待时间:3600000ms。时间:2014年8月29日   下午3:14:43

         

    Job 4在线程上开始:14。等待时间:15000ms。时间:2014年8月29日   下午3:14:43

         

    作业0在线程上开始:7。等待时间:600000ms。时间:2014年8月29日   下午3:14:43

         

    作业1开始于主题:12。等待时间:900000ms。时间:2014年8月29日   下午3:14:43

         

    Job 3在线程上开始:11。等待时间:120000ms。时间:2014年8月29日   下午3:14:43

         工作4完成了主题:14。 8/29/2014 3:14:58 PM

         

    Job 5在线程上开始:14。等待时间:1800000ms。时间:2014年8月29日   下午3:14:58

         

    作业3完成了主题:11。 8/29/2014 3:16:43 PM

         

    Job 6从线程开始:11。等待时间:1200000ms。时间:2014年8月29日   下午3:16:43

         

    作业0在线程上完成:7。 8/29/2014 3:24:43 PM

         

    Job 7在线程上开始:7。等待时间:30000ms。时间:2014年8月29日3:24:43   PM

         

    作业7完成了主题:7。 8/29/2014 3:25:13 PM

         

    Job 8在线程上开始:7。等待时间:100000ms。时间:2014年8月29日   下午3:25:13

         

    作业8完成了主题:7。 8/29/2014 3:26:53 PM

         

    Job 9在线程上开始:7。等待时间:900000ms。时间:2014年8月29日   下午3:26:53

         

    作业1完成了主题:12。 8/29/2014 3:29:43 PM

         

    Job 10在线程上开始:12。等待时间:300000ms。时间:2014年8月29日   下午3:29:43

         

    作业10完成了主题:12。 8/29/2014 3:34:43 PM

         

    Job 11开始于主题:12。等待时间:600000ms。时间:2014年8月29日   下午3:34:43

         

    作业6完成了主题:11。 8/29/2014 3:36:43 PM

         

    Job 12从线程开始:11。等待时间:300000ms。时间:2014年8月29日   下午3:36:43

         

    作业12完成了主题:11。 8/29/2014 3:41:43 PM

         

    Job 13在线程上开始:11。等待时间:100000ms。时间:2014年8月29日   下午3:41:43

         

    作业9完成了主题:7。 8/29/2014 3:41:53 PM

         

    Job 14从线程开始:7。等待时间:300000ms。时间:2014年8月29日   下午3:41:53

         

    作业13完成了主题:11。 8/29/2014 3:43:23 PM

         

    工作11完成了主题:12。 8/29/2014 3:44:43 PM

         

    作业5完成了主题:14。 8/29/2014 3:44:58 PM

         

    作业14完成了主题:7。 8/29/2014 3:46:53 PM

         

    作业2完成了主题:13。 8/29/2014 4:14:43 PM

3 个答案:

答案 0 :(得分:5)

您可以使用TPL Dataflow轻松实现所需目标。

你可以做的是使用BufferBlock<T>,这是一个用于存储数据的缓冲区,并将其与ActionBlock<T>链接在一起,这将消耗这些请求,因为它们来自BufferBlock<T>

现在,这里的美妙之处在于您可以指定ActionBlock<T>使用ExecutionDataflowBlockOptions类同时处理的请求数量。

这是一个简化的控制台版本,可以处理一堆数字,因为它们会进入,打印出他们的姓名和Thread.ManagedThreadID

private static void Main(string[] args)
{
    var bufferBlock = new BufferBlock<int>();

    var actionBlock =
        new ActionBlock<int>(i => Console.WriteLine("Reading number {0} in thread {1}",
                                  i, Thread.CurrentThread.ManagedThreadId),
                             new ExecutionDataflowBlockOptions 
                                 {MaxDegreeOfParallelism = 5});

    bufferBlock.LinkTo(actionBlock);
    Produce(bufferBlock);

    Console.ReadKey();
}

private static void Produce(BufferBlock<int> bufferBlock)
{
    foreach (var num in Enumerable.Range(0, 500))
    {
        bufferBlock.Post(num);
    }
}

如果需要,您还可以使用等待的BufferBlock.SendAsync

异步发布它们

这样,您可以让TPL为您处理所有限制,而无需手动执行。

答案 1 :(得分:3)

你可以使用BlockingCollection它可以正常工作,但它是在async-await之前构建的,因此它会同步阻塞,在大多数情况下可能会降低可扩展性。

正如Yuval Itzchakov建议的那样,您最好使用async就绪TPL Dataflow。您只需要ActionBlock MaxDegreeOfParallelism同时处理每个项目,block.Post(item)为5,您可以同步(await block.SendAsync(item))或异步(private static void Main() { var block = new ActionBlock<Job>( async job => await job.ProcessAsync(), new ExecutionDataflowBlockOptions {MaxDegreeOfParallelism = 5}); for (var i = 0; i < 50; i++) { block.Post(new Job()); } Console.ReadKey(); } )将作品发布到其中:< / p>

{{1}}

答案 2 :(得分:0)

您可以使用this answer中的SemaphoreSlimthis answer中的ForEachAsync来执行此操作。