我有以下情景。
我从数据库中取出50个作业到阻塞集合中。
每项工作都是一项长期工作。 (可能是)。所以我想在一个单独的线程中运行它们。 (我知道 - 最好将它们作为Task.WhenAll运行,然后让TPL弄明白 - 但我想同时控制多少次运行)
说我想同时运行其中的5个(可配置)
我创建了5个任务(TPL),每个作业一个并行并行运行。
我想要做的是,只要第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 PMJob 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
答案 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中的SemaphoreSlim
或this answer中的ForEachAsync
来执行此操作。