我有一个1000个输入消息的集合要处理。我正在循环输入集合并为每条消息启动新任务以进行处理。
//Assume this messages collection contains 1000 items
var messages = new List<string>();
foreach (var msg in messages)
{
Task.Factory.StartNew(() =>
{
Process(msg);
});
}
我们能猜出当时同时处理多少个最大消息(假设是普通的四核处理器),还是我们可以限制当时要处理的最大消息数?
如何确保以与Collection相同的顺序/顺序处理此消息?
答案 0 :(得分:30)
您可以使用Parallel.Foreach
并依赖MaxDegreeOfParallelism
代替。
Parallel.ForEach(messages, new ParallelOptions {MaxDegreeOfParallelism = 10},
msg =>
{
// logic
Process(msg);
});
答案 1 :(得分:23)
SemaphoreSlim在这种情况下是一个非常好的解决方案,我强烈建议OP尝试这个,但@ Manoj的答案有缺陷,如评论中所述。在产生这样的任务之前应该等待。
更新后的答案:由于@Vasyl指出信号量可能在任务完成之前被处理,并且在调用Release()
方法时会引发异常,因此在退出使用块之前必须等待完成所有已创建的任务。
int maxConcurrency=10;
var messages = new List<string>();
using(SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
{
List<Task> tasks = new List<Task>();
foreach(var msg in messages)
{
concurrencySemaphore.Wait();
var t = Task.Factory.StartNew(() =>
{
try
{
Process(msg);
}
finally
{
concurrencySemaphore.Release();
}
});
tasks.Add(t);
}
Task.WaitAll(tasks.ToArray());
}
回答评论
对于那些想要了解如何在没有Task.WaitAll
的情况下处理信号量的人
在控制台应用程序中运行以下代码,将引发此异常。
System.ObjectDisposedException:'信号量已被释放。'
static void Main(string[] args)
{
int maxConcurrency = 5;
List<string> messages = Enumerable.Range(1, 15).Select(e => e.ToString()).ToList();
using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
{
List<Task> tasks = new List<Task>();
foreach (var msg in messages)
{
concurrencySemaphore.Wait();
var t = Task.Factory.StartNew(() =>
{
try
{
Process(msg);
}
finally
{
concurrencySemaphore.Release();
}
});
tasks.Add(t);
}
// Task.WaitAll(tasks.ToArray());
}
Console.WriteLine("Exited using block");
Console.ReadKey();
}
private static void Process(string msg)
{
Thread.Sleep(2000);
Console.WriteLine(msg);
}
答案 2 :(得分:5)
我认为使用Parallel LINQ
会更好 Parallel.ForEach(messages ,
new ParallelOptions{MaxDegreeOfParallelism = 4},
x => Process(x);
);
其中x是MaxDegreeOfParallelism
答案 3 :(得分:1)
您可以像这样简单地设置最大并发度:
int maxConcurrency=10;
var messages = new List<1000>();
using(SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
{
foreach(var msg in messages)
{
Task.Factory.StartNew(() =>
{
concurrencySemaphore.Wait();
try
{
Process(msg);
}
finally
{
concurrencySemaphore.Release();
}
});
}
}
答案 4 :(得分:0)
您可以创建自己的TaskScheduler并覆盖那里的QueueTask。
protected virtual void QueueTask(Task task)
然后你可以做任何你喜欢的事。
这里有一个例子:
Limited concurrency level task scheduler (with task priority) handling wrapped tasks
答案 5 :(得分:0)
如果您需要按顺序排队(处理可能以任何顺序完成),则不需要信号量。如果陈述正常,那么老式:
const int maxConcurrency = 5;
List<Task> tasks = new List<Task>();
foreach (var arg in args)
{
var t = Task.Run(() => { Process(arg); } );
tasks.Add(t);
if(tasks.Count >= maxConcurrency)
Task.WaitAny(tasks.ToArray());
}
Task.WaitAll(tasks.ToArray());
答案 6 :(得分:0)
public static void RunTasks(List<NamedTask> importTaskList)
{
List<NamedTask> runningTasks = new List<NamedTask>();
try
{
foreach (NamedTask currentTask in importTaskList)
{
currentTask.Start();
runningTasks.Add(currentTask);
if (runningTasks.Where(x => x.Status == TaskStatus.Running).Count() >= MaxCountImportThread)
{
Task.WaitAny(runningTasks.ToArray());
}
}
Task.WaitAll(runningTasks.ToArray());
}
catch (Exception ex)
{
Log.Fatal("ERROR!", ex);
}
}
答案 7 :(得分:-1)
您可以使用BlockingCollection
,如果已达到消耗量收集限制,则生产将停止生产,直到消耗过程完成。我发现这种模式比add_action( 'woocommerce_product_query', array($this, 'b2bking_hide_products_category_visibility') );
function b2bking_hide_products_category_visibility($q ){
$tax_query = (array) $q->get( 'tax_query' );
$tax_query[] = array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => $visiblecategories,
'operator' => 'IN'
);
$q->set( 'tax_query', $tax_query );
// Set query to only select products that are in "default" category mode, not manual visibility mode
$q->set('meta_query', array(
array(
'key' => 'b2bking_product_visibility_override',
'value' => 'default',
)
));
/* SEPARATE META QUERY
$q->set('meta_query', array(
'relation' => 'AND',
array(
'relation' => 'OR',
array(
'key' => 'b2bking_group_'.$currentusergroupidnr,
'value' => '1'
),
array(
'key' => 'b2bking_user_'.$currentuserlogin,
'value' => '1'
)
),
array(
'key' => 'b2bking_product_visibility_override',
'value' => 'manual',
)
));
*/
}
}
更易于理解和实施。
SemaphoreSlim
请注意,int TasksLimit = 10;
BlockingCollection<Task> tasks = new BlockingCollection<Task>(new ConcurrentBag<Task>(), TasksLimit);
void ProduceAndConsume()
{
var producer = Task.Factory.StartNew(RunProducer);
var consumer = Task.Factory.StartNew(RunConsumer);
try
{
Task.WaitAll(new[] { producer, consumer });
}
catch (AggregateException ae) { }
}
void RunConsumer()
{
foreach (var task in tasks.GetConsumingEnumerable())
{
task.Start();
}
}
void RunProducer()
{
for (int i = 0; i < 1000; i++)
{
tasks.Add(new Task(() => Thread.Sleep(1000), TaskCreationOptions.AttachedToParent));
}
}
和RunProducer
产生了两个独立的任务。
答案 8 :(得分:-1)
如果您的Process
方法是异步的,则不能使用Task.Factory.StartNew
,因为它在异步委托中不能很好地发挥作用。使用它时,还有其他一些细微差别(例如,请参见this)。
在这种情况下,正确的方法是使用Task.Run
。这是针对异步Process方法修改的@ClearLogic答案。
static void Main(string[] args)
{
int maxConcurrency = 5;
List<string> messages = Enumerable.Range(1, 15).Select(e => e.ToString()).ToList();
using (SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(maxConcurrency))
{
List<Task> tasks = new List<Task>();
foreach (var msg in messages)
{
concurrencySemaphore.Wait();
var t = Task.Run(async () =>
{
try
{
await Process(msg);
}
finally
{
concurrencySemaphore.Release();
}
});
tasks.Add(t);
}
Task.WaitAll(tasks.ToArray());
}
Console.WriteLine("Exited using block");
Console.ReadKey();
}
private static async Task Process(string msg)
{
await Task.Delay(2000);
Console.WriteLine(msg);
}