.net 并行ForEach太慢了

.net 并行ForEach太慢了,.net,task-parallel-library,task,.net,Task Parallel Library,Task,我想再做一只蜘蛛。为此,我决定继续执行任务。我创造了一点概念证明。它是有效的,但我认为,它有点慢 class Program { static void Main(string[] args) { InitializeUrls(); Start(); Console.ReadKey(); } private static void InitializeUrls() { _random

我想再做一只蜘蛛。为此,我决定继续执行任务。我创造了一点概念证明。它是有效的,但我认为,它有点慢

class Program
{
    static void Main(string[] args)
    {
        InitializeUrls();

        Start();


        Console.ReadKey();
    }

    private static void InitializeUrls()
    {
        _random = new Random();

        List<int> numbers = Enumerable.Range(0, 100).ToList();
        foreach (int number in numbers)
            _urls.Add(number.ToString());
    }

    private static readonly BlockingCollection<string> _urls = new BlockingCollection<string>();

    private static readonly TaskFactory _factory = new TaskFactory();

    private static CancellationTokenSource _tokenSource;

    private static Task _task;

    private static Random _random;

    public static void Start()
    {
        _tokenSource = new CancellationTokenSource();
        _task = _factory.StartNew(
            () =>
            {
                try
                {
                    Parallel.ForEach(
                        _urls.GetConsumingEnumerable(),
                        new ParallelOptions
                        {
                            MaxDegreeOfParallelism = 100, //number of threads running parallel
                            CancellationToken = _tokenSource.Token
                        },
                        (url, loopState) =>
                        {
                            if (!_tokenSource.IsCancellationRequested)
                            {
                                //here is the action
                                int waitTime = 5;// _random.Next(0, 15);

                                Console.WriteLine(string.Format("url {0}\ttime {1}\tthreadID {2}", url, waitTime,Thread.CurrentThread.ManagedThreadId));
                                Thread.Sleep(waitTime * 1000);
                            }
                            else
                            {
                                //stop

                                loopState.Stop();
                            }
                        });
                }
                catch (OperationCanceledException exception)
                {
                    Console.WriteLine("Error when ending the operation", exception.ToString());
                }
                catch (Exception exception)
                {
                    Console.WriteLine("General exception", exception);
                }
            },
            _tokenSource.Token);
    }
}
类程序
{
静态void Main(字符串[]参数)
{
初始化eurls();
Start();
Console.ReadKey();
}
私有静态无效初始值设定项EURLS()
{
_随机=新随机();
列表编号=可枚举的.Range(01100).ToList();
foreach(整数中的整数)
_Add(number.ToString());
}
私有静态只读BlockingCollection _URL=new BlockingCollection();
私有静态只读TaskFactory _factory=new TaskFactory();
私有静态CancellationTokenSource\u tokenSource;
私有静态任务_任务;
私有静态随机(u Random),;
公共静态void Start()
{
_tokenSource=新的CancellationTokenSource();
_任务=_factory.StartNew(
() =>
{
尝试
{
并行ForEach(
_url.getConsuminegumerable(),
新的并行选项
{
MaxDegreeOfParallelism=100,//并行运行的线程数
CancellationToken=\u tokenSource.Token
},
(url,loopState)=>
{
如果(!\u tokenSource.IsCancellationRequested)
{
//这就是行动
int waitTime=5;//_random.Next(0,15);
WriteLine(string.Format(“url{0}\ttime{1}\tthreadID{2}”、url、waitTime、Thread.CurrentThread.ManagedThreadId));
线程睡眠(等待时间*1000);
}
其他的
{
//停止
loopState.Stop();
}
});
}
捕获(操作取消异常异常)
{
Console.WriteLine(“结束操作时出错”,exception.ToString());
}
捕获(异常)
{
Console.WriteLine(“一般异常”,异常);
}
},
_tokenSource.Token);
}
}
如您所见,我可以设置一次运行的线程数。当我将其设置为1时,效果很好,它将url写入控制台并等待5秒钟。
当我将它设置为100时,我希望它能立即创建100个任务,但如果您运行它,它不会。它获取URL的速度非常慢。你知道为什么会发生这种情况吗?

如果你的工作是CPU受限的,那么添加比内核更多的线程不会有任何好处

如果您的工作不受CPU限制(例如,睡眠),则应使用异步(
await Task.WhenAll(stuff.Select(async s=>await…)
),这样您就不需要任何线程

当我将其设置为100时,我希望它能够创建100个任务 立即

这是你的错误。您设置的变量不是“DegreeOfParallelism”,而是“MaxDegreeOfParallelism”。
Parallel.ForEach
将以少量任务开始,然后随着工作的完成逐渐增加到您定义的最大值

我非常推荐你阅读微软的免费电子书。它讨论了类似于
Parallel.ForEach
等的行为

如果你想立即得到100个线程,你只需要使用一个普通的ForEach并自己排队工作。您需要某种速率限制器来限制最大并行度

var degreeOfParallelism = new Semaphore(100, 100);

foreach(var loopUrl in _urls.GetConsumingEnumerable())
{
    //If you are on C# 5 this line is not necessary.
    var url = loopUrl;

    if (_tokenSource.IsCancellationRequested)
    {
        //Stop
        break;
    }

    //Takes one slot up in the pool of 100.
    degreeOfParallelism.WaitOne();

    ThreadPool.QueueUserWorkItem((state) =>
    {
        try
        {    
            //here is the action
            int waitTime = 5;// _random.Next(0, 15);

            Console.WriteLine(string.Format("url {0}\ttime {1}\tthreadID {2}", url, waitTime,Thread.CurrentThread.ManagedThreadId));
            Thread.Sleep(waitTime * 1000);
        }
        finally
        {
            //Release a item back to the pool.
            degreeOfParallelism.Release();
        }            
    });
}

然而,如果你正在做一个网络爬虫并且在.NET4.5上,你应该根本不需要使用线程。相反,使用函数的
xxxxxancy()
版本,您可以保留100个任务的列表,只需执行
Task.whany(您的任务列表)
即可检测一个任务何时完成。

实际上,您不会运行100个单独的线程。这里有一个指向另一个堆栈溢出问题的链接,这个问题与相同的事情有关:好的,我理解为什么这只限于我拥有的内核数(4)。但是我想我可以有任意多个线程并行运行。我很确定我已经看到很多任务都是在做其他事情。为什么这种构造不同?据我所知,可以有尽可能多的“任务”排队,但它仍然与CPU可以处理的线程数有关。我个人没有资格详细解释,但是有很多关于网络上并行计算的信息和理论。祝你好运