如何限制c#中的最大并行任务数

时间:2016-04-12 05:50:26

标签: c# .net asynchronous

我有一个1000个输入消息的集合要处理。我正在循环输入集合并为每条消息启动新任务以进行处理。

//Assume this messages collection contains 1000 items
var messages = new List<string>();

foreach (var msg in messages)
{
   Task.Factory.StartNew(() =>
   {
    Process(msg);
   });
 }

我们能猜出当时同时处理多少个最大消息(假设是普通的四核处理器),还是我们可以限制当时要处理的最大消息数?

如何确保以与Collection相同的顺序/顺序处理此消息?

9 个答案:

答案 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);
}