简体   繁体   English

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

[英]blocking collection process n items at a time - continuing as soon as 1 is done

I have the following Scenario. 我有以下场景。

  1. I take 50 jobs from the database into a blocking collection. 我从数据库中将50个作业转换为阻塞集合。

  2. Each job is a long running one. 每项工作都是一项长期工作。 (potentially could be). (可能是)。 So I want to run them in a separate thread. 所以我想在一个单独的线程中运行它们。 (I know - it may be better to run them as Task.WhenAll and let the TPL figure it out - but I want to control how many runs simultaneously) (我知道 - 将它们作为Task.WhenAll运行并让TPL解决它可能会更好 - 但我想控制同时运行多少次)

  3. Say I want to run 5 of them simultaneously (configurable) 说我想同时运行其中的5个(可配置)

  4. I create 5 tasks (TPL), one for each job and run them in parallel. 我创建了5个任务(TPL),每个作业一个并行并行运行。

What I want to do is to pick up the next Job in the blocking collection as soon as one of the jobs from step 4 is complete and keep going until all 50 are done. 我想要做的是,一旦第4步中的一个作业完成,就立即在阻塞集合中选择下一个作业,并继续进行直到完成所有50个作业。

I am thinking of creating a Static blockingCollection and a TaskCompletionSource which will be invoked when a job is complete and then it can call the consumer again to pick one job at a time from the queue. 我正在考虑创建一个Static blockingCollection和一个TaskCompletionSource,它将在作业完成时调用,然后它可以再次调用使用者从队列中一次选择一个作业。 I would also like to call async/await on each job - but that's on top of this - not sure if that has an impact on the approach. 我还想在每项工作上调用async / await - 但这是最​​重要的 - 不确定这是否对该方法有影响。

Is this the right way to accomplish what I'm trying to do? 这是完成我想要做的事情的正确方法吗?

Similar to this link, but catch is that I want to process the next Job as soon as one of the first N items are done. 链接类似,但是捕获的是我想在前N个项目中的一个完成后立即处理下一个作业。 Not after all N are done. 毕竟N都没有完成。

Update : 更新:

Ok, I have this code snippet doing exactly what I want, if someone wants to use it later. 好的,如果有人想在以后使用它,我的代码片段正是我想要的。 As you can see below, 5 threads are created and each thread starts the next job when it is done with current. 如下所示,创建了5个线程,每个线程在完成当前操作时启动下一个作业。 Only 5 threads are active at any given time. 在任何给定时间只有5个线程处于活动状态。 I understand this may not work 100% like this always, and will have performance issues of context switching if used with one cpu/core. 我知道这可能不会像这样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 started on thread :13. 工作2开始于主题:13。 wait time:3600000ms. 等待时间:3600000ms。 Time:8/29/2014 3:14:43 PM 时间:2014年8月29日下午3:14:43

Job 4 started on thread :14. 工作4开始于主题:14。 wait time:15000ms. 等待时间:15000ms。 Time:8/29/2014 3:14:43 PM 时间:2014年8月29日下午3:14:43

Job 0 started on thread :7. 作业0在线程上开始:7。 wait time:600000ms. 等待时间:600000ms。 Time:8/29/2014 3:14:43 PM 时间:2014年8月29日下午3:14:43

Job 1 started on thread :12. 作业1开始于主题:12。 wait time:900000ms. 等待时间:900000ms。 Time:8/29/2014 3:14:43 PM 时间:2014年8月29日下午3:14:43

Job 3 started on thread :11. 工作3开始于主题:11。 wait time:120000ms. 等待时间:120000ms。 Time:8/29/2014 3:14:43 PM 时间:2014年8月29日下午3:14:43

job 4 finished on thread :14. 工作4完成了主题:14。 8/29/2014 3:14:58 PM 2014年8月29日下午3:14:58

Job 5 started on thread :14. 作业5开始于主题:14。 wait time:1800000ms. 等待时间:1800000ms。 Time:8/29/2014 3:14:58 PM 时间:2014年8月29日下午3:14:58

job 3 finished on thread :11. 工作3完成线程:11。 8/29/2014 3:16:43 PM 2014年8月29日下午3:16:43

Job 6 started on thread :11. Job 6从线程开始:11。 wait time:1200000ms. 等待时间:1200000ms。 Time:8/29/2014 3:16:43 PM 时间:2014年8月29日下午3:16:43

job 0 finished on thread :7. 作业0完成了主题:7。 8/29/2014 3:24:43 PM 2014年8月29日下午3:24:43

Job 7 started on thread :7. Job 7从线程开始:7。 wait time:30000ms. 等待时间:30000ms。 Time:8/29/2014 3:24:43 PM 时间:2014年8月29日下午3:24:43

job 7 finished on thread :7. 工作7完成线程:7。 8/29/2014 3:25:13 PM 2014年8月29日下午3:25:13

Job 8 started on thread :7. Job 8开始于主题:7。 wait time:100000ms. 等待时间:100000ms。 Time:8/29/2014 3:25:13 PM 时间:2014年8月29日下午3:25:13

job 8 finished on thread :7. 作业8完成了主题:7。 8/29/2014 3:26:53 PM 2014年8月29日下午3:26:53

Job 9 started on thread :7. Job 9从线程开始:7。 wait time:900000ms. 等待时间:900000ms。 Time:8/29/2014 3:26:53 PM 时间:2014年8月29日下午3:26:53

job 1 finished on thread :12. 工作1完成线程:12。 8/29/2014 3:29:43 PM 2014年8月29日下午3:29:43

Job 10 started on thread :12. 作业10开始于主题:12。 wait time:300000ms. 等待时间:300000ms。 Time:8/29/2014 3:29:43 PM 时间:2014年8月29日下午3:29:43

job 10 finished on thread :12. 作业10完成了主题:12。 8/29/2014 3:34:43 PM 8/29/2014 3:34:43 PM

Job 11 started on thread :12. 工作11开始于主题:12。 wait time:600000ms. 等待时间:600000ms。 Time:8/29/2014 3:34:43 PM 时间:2014年8月29日下午3:34:43

job 6 finished on thread :11. 工作6完成线程:11。 8/29/2014 3:36:43 PM 2014年8月29日下午3:36:43

Job 12 started on thread :11. 工作12开始于主题:11。 wait time:300000ms. 等待时间:300000ms。 Time:8/29/2014 3:36:43 PM 时间:2014年8月29日下午3:36:43

job 12 finished on thread :11. 工作12完成了主题:11。 8/29/2014 3:41:43 PM 2014年8月29日下午3:41:43

Job 13 started on thread :11. Job 13开始于主题:11。 wait time:100000ms. 等待时间:100000ms。 Time:8/29/2014 3:41:43 PM 时间:2014年8月29日下午3:41:43

job 9 finished on thread :7. 作业9完成了主题:7。 8/29/2014 3:41:53 PM 2014年8月29日下午3:41:53

Job 14 started on thread :7. Job 14从线程开始:7。 wait time:300000ms. 等待时间:300000ms。 Time:8/29/2014 3:41:53 PM 时间:2014年8月29日下午3:41:53

job 13 finished on thread :11. 工作13完成了主题:11。 8/29/2014 3:43:23 PM 2014年8月29日下午3:43:23

job 11 finished on thread :12. 工作11完成了主题:12。 8/29/2014 3:44:43 PM 2014年8月29日下午3:44:43

job 5 finished on thread :14. 工作5完成线程:14。 8/29/2014 3:44:58 PM 8/29/2014 3:44:58 PM

job 14 finished on thread :7. 作业14完成了主题:7。 8/29/2014 3:46:53 PM 8/29/2014 3:46:53 PM

job 2 finished on thread :13. 工作2完成了主题:13。 8/29/2014 4:14:43 PM 8/29/2014 4:14:43 PM

You can easily achieve what you need using TPL Dataflow . 您可以使用TPL Dataflow轻松实现所需。

What you can do is use BufferBlock<T> , which is a buffer for storing you data, and link it together with an ActionBlock<T> which will consume those requests as they're coming in from the BufferBlock<T> . 你可以做的是使用BufferBlock<T> ,它是一个用于存储数据的缓冲区,并将它与ActionBlock<T>链接在一起,当它们从BufferBlock<T>进入时将消耗这些请求。

Now, the beauty here is that you can specify how many requests you want the ActionBlock<T> to handle concurrently using the ExecutionDataflowBlockOptions class. 现在,这里的美妙之处在于,您可以指定ActionBlock<T>使用ExecutionDataflowBlockOptions类并发处理的请求数。

Here's a simplified console version, which processes a bunch of numbers as they're coming in, prints their name and Thread.ManagedThreadID : 这是一个简化的控制台版本,在它们进入时处理一堆数字,打印它们的名字和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);
    }
}

You can also post them asynchronously if needed, using the awaitable BufferBlock.SendAsync 如果需要,您还可以使用等待的BufferBlock.SendAsync异步发布它们

That way, you let the TPL handle all the throttling for you without needing to do it manually. 这样,您可以让TPL为您处理所有限制,而无需手动执行。

You can use BlockingCollection and it will work just fine, but it was built before async-await so it blocks synchronously which could be less scalable in most cases. 您可以使用BlockingCollection ,它可以正常工作,但它是在async-await之前构建的,因此它会同步阻塞,这在大多数情况下可能会降低。

You're better off using async ready TPL Dataflow as Yuval Itzchakov suggested. Yuval Itzchakov建议你最好使用async ready TPL Dataflow All you need is an ActionBlock that processes each item concurrently with a MaxDegreeOfParallelism of 5 and you post your work to it synchronously ( block.Post(item) ) or asynchronously ( await block.SendAsync(item) ): 所有你需要的是一个ActionBlock ,它同时处理每个项目的MaxDegreeOfParallelism为5,你同步发布你的工作( block.Post(item) )或异步( 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();
}

您可以使用SemaphoreSlim执行此操作,例如在此答案中 ,或使用ForEachAsync如此答案中所示

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

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