C# 我应该在服务器上使用Parallel.ForEach同时发出许多web请求吗

C# 我应该在服务器上使用Parallel.ForEach同时发出许多web请求吗,c#,multithreading,parallel-processing,threadpool,parallel.foreach,C#,Multithreading,Parallel Processing,Threadpool,Parallel.foreach,我已经读了很多关于Parallel.ForEach的书,但还没有找到一个可靠的答案来回答我的问题 我们有一个Windows服务,它每隔几分钟从多个数据库中提取行,并使用foreach循环,通过web请求发送这些行以完成操作。因此,所有这些web请求当前都是按顺序完成的,而且耗时太长,因此我们希望并行运行它们 我最初的调查让我相信a是最好的,生产者每几分钟将行放入线程安全队列,在服务初始化期间,我只需启动一些消费者线程(例如10个,但可能是100个或更多),它不断检查队列,查看是否有行需要通过we

我已经读了很多关于Parallel.ForEach的书,但还没有找到一个可靠的答案来回答我的问题

我们有一个Windows服务,它每隔几分钟从多个数据库中提取行,并使用foreach循环,通过web请求发送这些行以完成操作。因此,所有这些web请求当前都是按顺序完成的,而且耗时太长,因此我们希望并行运行它们

我最初的调查让我相信a是最好的,生产者每几分钟将行放入线程安全队列,在服务初始化期间,我只需启动一些消费者线程(例如10个,但可能是100个或更多),它不断检查队列,查看是否有行需要通过web请求发送

一位同事建议将我们的foreach循环改为Parallel.foreach。我对此的第一个担忧是,ForEach将阻止所有操作,直到枚举中的所有项都完成为止,因此,如果它有10个项,9个项在5秒内完成,一个项在5分钟内完成,那么它实际上什么也不做,只做了4分55秒的一个请求。这可以通过在新线程中执行Parallel.ForEach来克服,如下所示:

Task.Factory.StartNew( () => Parallel.ForEach<Item>(items, item => DoSomething(item)));
下面是使用Parallel.ForEach时代码外观的简化版本:

void FunctionGetsCalledEvery2Minutes()
{
    // Synchronously loop over each database that we need to check.
    foreach (var database in databasesToCheck)
    {
        // Get the rows from this database.
        var rows = database.GetRowsFromTable();

        // Asynchronously send each row to a web service to be processed, processing no more than 30 at a time.
        // Call the Parallel.ForEach from a new Task so that it does not block until all rows have been sent.
        Task.Factory.StartNew(() => Parallel.ForEach<DatabaseRow>(rows, new ParallelOptions() { MaxDegreeOfParallelism = 30 }, SendRequestToWebServiceToBeProcessed));
    }
}
void函数GetScalledEvery2minutes()
{
//在我们需要检查的每个数据库上同步循环。
foreach(数据库中的var数据库stocheck)
{
//从该数据库获取行。
var rows=database.GetRowsFromTable();
//异步地将每一行发送到要处理的web服务,一次处理不超过30行。
//从新任务调用Parallel.ForEach,以便在发送所有行之前不会阻塞它。
Task.Factory.StartNew(()=>Parallel.ForEach(行,新的ParallelOptions(){maxdegreeofpparallelism=30},SendRequestToWebServiceToBeProcessed));
}
}
下面是使用producer-consumer的代码的简化版本:

private System.Collections.Concurrent.BlockingCollection<DatabaseRow> _threadSafeQueue = new System.Collections.Concurrent.BlockingCollection<DatabaseRow>();
void FunctionGetsCalledEvery2Minutes()
{
    // Synchronously loop over each database that we need to check.
    foreach (var database in databasesToCheck)
    {
        // Get the rows from this database.
        var rows = database.GetRowsFromTable();

        // Add the rows to the queue to be processed by the consumer threads.
        foreach (var row in rows)
        {
            _threadSafeQueue.Add(row);
        }
    }
}

void ConsumerCode()
{
    // Take a request off the queue and send it away to be processed.
    var request = _threadSafeQueue.Take();
    SendRequestToWebServiceToBeProcessed(request);
}

void CreateConsumerThreadsOnApplicationStartup(int numberOfConsumersToCreate)
{
    // Create the number of consumer threads specified.
    for (int i = 0; i < numberOfConsumersTo; i++)
    {
        Task.Factory.StartNew(ConsumerCode);
    }
}
private System.Collections.Concurrent.BlockingCollection\u threadSafeQueue=new System.Collections.Concurrent.BlockingCollection();
void函数GetScalledEvery2minutes()
{
//在我们需要检查的每个数据库上同步循环。
foreach(数据库中的var数据库stocheck)
{
//从该数据库获取行。
var rows=database.GetRowsFromTable();
//将行添加到要由使用者线程处理的队列中。
foreach(行中的变量行)
{
_threadSafeQueue.Add(行);
}
}
}
void ConsumerCode()
{
//从队列中取出一个请求并将其发送出去以进行处理。
var request=_threadSafeQueue.Take();
SendRequestToWebServiceToBeProcessed(请求);
}
void CreateConsumerThreadsOnApplicationStartup(int numberOfConsumersToCreate)
{
//创建指定的使用者线程数。
for(int i=0;i
在本例中,我有一个同步生产者,但我可以轻松地为每个要轮询的数据库启动一个异步生产者线程

这里需要注意的一点是,在Parallel.ForEach示例中,我将其限制为一次最多只能处理30个线程,但这仅适用于该实例。如果2分钟过去了,并且Parallel.ForEach循环仍然有10个尚未完成的请求,那么它将启动30个新线程,总共40个线程同时运行。因此,如果web请求的超时时间为10分钟,我们很容易就会遇到这样的情况,即有150个线程同时运行(10分钟/2分钟=调用的函数5次*30个线程/实例=150)。这是一个潜在的问题,如果我增加了允许的最大线程数,或者在小于2分钟的时间间隔内开始调用该函数,我可能很快就会同时运行数千个线程,在服务器上消耗的资源比我想要的要多。这是一个合理的担忧吗?消费者-生产者方法没有这个问题;它将只运行我为numberOfConsumersToCreate变量指定的线程数

有人提到我应该为此使用TPL数据流,但我以前从未使用过它们,我不想在这个项目上花费太多时间。如果TPL数据流仍然是我想知道的最佳选择,但我也想知道这两种方法(Parallel.ForEach与Producer-Consumer)中哪一种更适合我的场景


希望这能提供更多的上下文,这样我就能得到更好的有针对性的答案。谢谢:)

如果您有许多短操作和偶尔的长操作,
Parallel.ForEach
将阻塞,直到所有操作完成。然而,当它在处理一个长请求时,它不会绑定所有的核心,只会绑定仍在工作的核心。请记住,当有许多项目正在处理时,它将尝试使用所有核心

编辑:

使用
MaxDegreeOfParallelism
属性,没有理由将其设置在CPU可以运行的线程数之上(受内核数和超线程度的限制)。事实上,只有将其减少到低于该值的数值才有用


因为阻塞不是并行的问题。ForEach虽然看起来很懒,但如果您的项目真的可以同时运行,它是非常合适的。

我对您的代码不太了解,但我有一些经验/建议

并行代码确实可以很快,这取决于在四核上启动数百个线程的内核数量并不理想,如果有4个线程(通常)会更好,我知道有些情况下会更好。但一般来说,您不需要考虑它,因为最新的.net版本可以处理它

还有霍维夫
private System.Collections.Concurrent.BlockingCollection<DatabaseRow> _threadSafeQueue = new System.Collections.Concurrent.BlockingCollection<DatabaseRow>();
void FunctionGetsCalledEvery2Minutes()
{
    // Synchronously loop over each database that we need to check.
    foreach (var database in databasesToCheck)
    {
        // Get the rows from this database.
        var rows = database.GetRowsFromTable();

        // Add the rows to the queue to be processed by the consumer threads.
        foreach (var row in rows)
        {
            _threadSafeQueue.Add(row);
        }
    }
}

void ConsumerCode()
{
    // Take a request off the queue and send it away to be processed.
    var request = _threadSafeQueue.Take();
    SendRequestToWebServiceToBeProcessed(request);
}

void CreateConsumerThreadsOnApplicationStartup(int numberOfConsumersToCreate)
{
    // Create the number of consumer threads specified.
    for (int i = 0; i < numberOfConsumersTo; i++)
    {
        Task.Factory.StartNew(ConsumerCode);
    }
}