在继续执行代码之前等待免费的线程插槽

时间:2015-02-09 15:38:27

标签: c# .net multithreading ssis .net-4.0

为线程完成队列行的最佳方法是什么,这样我才能拥有最大数量的线程,如果我已经拥有那么多代码,则在继续之前等待一个空闲插槽..

我所说的伪代码示例,我确信这可以用更好的方式完成......

(请检查下面的其他要求)

private int _MaxThreads = 10;
private int _CurrentThreads = 0;

public void main(string[] args)
{
    List<object> listWithLotsOfItems = FillWithManyThings();

    while(listWithLotsOfItems.Count> 0)
    {
        // get next item that needs to be worked on
        var item = listWithLotsOfItems[0];
        listWithLotsOfItems.RemoveAt(0);

        // IMPORTANT!, more items can be added as we go.
        listWithLotsOfItems.AddRange(AddMoreItemsToBeProcessed());

        // wait for free thread slot
        while (_CurrentThreads >= _MaxThreads)
            Thread.Sleep(100);

        Interlocked.Increment(ref _CurrentThreads); // risk of letting more than one thread through here...
        Thread t = new Thread(new ParameterizedThreadStart(WorkerThread(item));
        t.Start();
    }
}

public void WorkerThread(object bigheavyObject)
{
    // do heavy work here
    Interlocked.Decrement(ref _CurrentThreads);
}

查看Sempahore,但似乎需要在线程内部运行,而不是在创建之前在外部运行。在这个例子中,在创建它之后,在线程内部使用信号量来暂停它,在我的情况下,在完成作业之前可能有超过100k的线程需要运行所以我宁愿在插槽可用之前不创建线程。 (link to semaphore example

在实际应用中,数据可以随着程序的进展添加到项目列表中,因此Parallel.ForEach也不会真正发挥作用(我正在做这在SSIS包中的脚本组件中将数据发送到非常慢的WCF)。

SSIS有.Net 4.0

3 个答案:

答案 0 :(得分:3)

所以,首先让我说你正在尝试做的只是在一个非常具体的安排中给你一点性能提升。尝试调整线程分配级别可能需要做很多工作,所以在继续之前一定要有充分的理由。

现在,首先,如果您想简单地排队工作,可以将它放在.NET线程池中。它只会分配最大配置的线程,并且任何不适合它们的工作(如果所有线程都忙)将排队等待线程可用。

最简单的方法是致电:

Task.Factory.StartNew(() => { /* Your code */});

这将创建一个TPL任务,并将其安排在默认任务调度程序上运行,该任务调度程序又将任务分配给线程池。

如果您需要在继续之前等待完成这些任务,可以将它们添加到集合中,然后使用Task.WaitAll(...)

var tasks = new List<Task>();

tasks.Add(Task.Factory.StartNew(() => { /* Your code */}));

// Before leaving the script.
Task.WaitAll(tasks);

但是,如果您需要更深入地控制这些任务的调度,您可以查看创建支持有限并发的自定义任务调度程序。 This MSDN article详细介绍了它,并建议了可能的实施方案,但这不是一项微不足道的任务。

答案 1 :(得分:2)

最简单的方法是使用Parallel.ForEach()重载,您可以选择MaxDegreeOfParallelism

以下是一个示例程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    public static class Program
    {
        private static void Main()
        {
            List<int> items = Enumerable.Range(1, 100).ToList();

            Parallel.ForEach(items, new ParallelOptions {MaxDegreeOfParallelism = 5}, process);
        }

        private static void process(int item)
        {
            Console.WriteLine("Processing " + item);
            Thread.Sleep(2000);
        }
    }
}

如果你运行它,你会看到它很快处理5个元素,然后在处理下一个元素块之前有一个延迟(由Thread.Sleep(2000)引起)。这是因为在此示例代码中,允许一次执行的线程不超过5个。

请注意,如果MaxDegreeOfParallelism超出了线程池的最小线程值,则可能需要一段时间才能启动所有线程。

原因是Parallel.ForEach()使用线程池线程 - 并且默认情况下线程池保持可用的线程数量。当创建超出此限制的线程时,会在每个新线程池线程创建之间引入延迟。

您可以使用ThreadPool.SetMinThreads()将最小线程池线程数设置为更高的值,但我不建议这样做。

但是,如果你想这样做,这里有一个例子,它将最小线程数设置为20:

ThreadPool.GetMinThreads(out dummy, out ioThreads);
ThreadPool.SetMinThreads(20, ioThreads);

如果您这样做,然后使用MaxDegreeOfParallelism = 20运行上一个代码,您将看到创建初始线程时不再有任何延迟。

答案 2 :(得分:0)

您是否考虑使用等待句柄?见this

您也可以使用Parallel.Foreach来管理线程创建。

希望它有所帮助;)