C# 针对多个web请求的最佳多线程方法

C# 针对多个web请求的最佳多线程方法,c#,multithreading,httpwebrequest,multitasking,C#,Multithreading,Httpwebrequest,Multitasking,我想创建一个程序来抓取和检查我的网站的http错误和其他事情。 我想用多个线程来实现这一点,这些线程应该接受url之类的参数来爬网。 尽管我希望X个线程处于活动状态,但仍有Y个任务等待执行 现在我想知道做这件事的最佳策略是什么:线程池、任务、线程或者其他什么?好吧,任务是一个很好的方法,因为这意味着您不必担心编写大量的“管道”代码 我建议您也去Joe Albahari的线程网站看看,这是一本非常好的线程入门书: 我建议使用threadPool。Is很容易使用,因为它有一些好处: “线程池将通过以

我想创建一个程序来抓取和检查我的网站的http错误和其他事情。 我想用多个线程来实现这一点,这些线程应该接受url之类的参数来爬网。 尽管我希望X个线程处于活动状态,但仍有Y个任务等待执行


现在我想知道做这件事的最佳策略是什么:线程池、任务、线程或者其他什么?

好吧,
任务
是一个很好的方法,因为这意味着您不必担心编写大量的“管道”代码

我建议您也去Joe Albahari的线程网站看看,这是一本非常好的线程入门书:


我建议使用threadPool。Is很容易使用,因为它有一些好处:

“线程池将通过以下方式为频繁和相对较短的操作提供好处: 重用已经创建的线程,而不是创建新线程(一个昂贵的过程) 在新工作项请求激增时限制线程创建速率(我相信这仅在.NET3.5中有)

如果您将100个线程池任务排队,它将只使用已创建的线程来服务这些请求(例如10个)。线程池将进行频繁检查(我相信在3.5 SP1中每500毫秒一次)如果有排队的任务,它将生成一个新线程。如果您的任务很快,那么新线程的数量将很小,并且将10个左右的线程重新用于短任务将比预先创建100个线程更快

如果您的工作负载始终有大量线程池请求传入,则线程池将通过上述进程在池中创建更多线程来调整自身以适应您的工作负载,从而有更多的线程可用于处理请求”


下面的示例演示了如何对一组任务进行排队,但限制并发运行的任务数。它使用
队列
跟踪准备运行的任务,并使用
字典
跟踪正在运行的任务。当任务完成时,它调用回调方法将自己从
字典中删除。
async
方法用于在空间可用时启动排队任务

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

namespace MinimalTaskDemo
{
    class Program
    {
        private static readonly Queue<Task> WaitingTasks = new Queue<Task>();
        private static readonly Dictionary<int, Task> RunningTasks = new Dictionary<int, Task>();
        public static int MaxRunningTasks = 100; // vary this to dynamically throttle launching new tasks 

        static void Main(string[] args)
        {
            var tokenSource = new CancellationTokenSource();
            var token = tokenSource.Token;
            Worker.Done = new Worker.DoneDelegate(WorkerDone);
            for (int i = 0; i < 1000; i++)  // queue some tasks
            {
                // task state (i) will be our key for RunningTasks
                WaitingTasks.Enqueue(new Task(id => new Worker().DoWork((int)id, token), i, token));
            }
            LaunchTasks();
            Console.ReadKey();
            if (RunningTasks.Count > 0)
            {
                lock (WaitingTasks) WaitingTasks.Clear();
                tokenSource.Cancel();
                Console.ReadKey();
            }
        }

        static async void LaunchTasks()
        {
            // keep checking until we're done
            while ((WaitingTasks.Count > 0) || (RunningTasks.Count > 0))
            {
                // launch tasks when there's room
                while ((WaitingTasks.Count > 0) && (RunningTasks.Count < MaxRunningTasks))
                {
                    Task task = WaitingTasks.Dequeue();
                    lock (RunningTasks) RunningTasks.Add((int)task.AsyncState, task);
                    task.Start();
                }
                UpdateConsole();
                await Task.Delay(300); // wait before checking again
            }
            UpdateConsole();    // all done
        }

        static void UpdateConsole()
        {
            Console.Write(string.Format("\rwaiting: {0,3:##0}  running: {1,3:##0} ", WaitingTasks.Count, RunningTasks.Count));
        }

        // callback from finished worker
        static void WorkerDone(int id)
        {
            lock (RunningTasks) RunningTasks.Remove(id);
        }
    }

    internal class Worker
    {
        public delegate void DoneDelegate(int taskId);
        public static DoneDelegate Done { private get; set; }
        private static readonly Random Rnd = new Random();

        public async void DoWork(object id, CancellationToken token)
        {
            for (int i = 0; i < Rnd.Next(20); i++)
            {
                if (token.IsCancellationRequested) break;
                await Task.Delay(100);  // simulate work
            }
            Done((int)id);
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用系统线程;
使用System.Threading.Tasks;
名称空间最小任务演示
{
班级计划
{
私有静态只读队列WaitingTasks=新队列();
private static readonly Dictionary RunningTasks=new Dictionary();
public static int MaxRunningTasks=100;//将其更改为动态限制启动新任务
静态void Main(字符串[]参数)
{
var tokenSource=new CancellationTokenSource();
var token=tokenSource.token;
Worker.Done=新Worker.DoneDelegate(WorkerDone);
for(int i=0;i<1000;i++)//对一些任务进行排队
{
//任务状态(i)将是运行任务的关键
排队(新任务(id=>newworker().DoWork((int)id,token),i,token));
}
启动任务();
Console.ReadKey();
如果(RunningTasks.Count>0)
{
lock(WaitingTasks)WaitingTasks.Clear();
tokenSource.Cancel();
Console.ReadKey();
}
}
静态异步void启动任务()
{
//继续检查直到我们完成
while((WaitingTasks.Count>0)| |(RunningTasks.Count>0))
{
//有空间时启动任务
而((WaitingTasks.Count>0)和&(RunningTasks.Count
我建议使用(异步)
Task
s下载数据,然后(在线程池上)进行处理

我建议不要限制任务,而是限制每个目标服务器的请求数。好消息:.NET

这使您的代码非常简单:

private static readonly HttpClient client = new HttpClient();
public async Task Crawl(string url)
{
  var html = await client.GetString(url);
  var nextUrls = await Task.Run(ProcessHtml(html));
  var nextTasks = nextUrls.Select(nextUrl => Crawl(nextUrl));
  await Task.WhenAll(nextTasks);
}
private IEnumerable<string> ProcessHtml(string html)
{
  // return all urls in the html string.
}

试试这个“最好”是很难定义的。我建议你研究一下“相关”的问题(右下),然后选择一个你认为最适合你申请的问题。也许任务是一条路要走,但仍然有很大的变化空间。嗨,使用
Rnd
for
循环的目的是什么?这一个=>
for(inti=0;i
@Shiva-循环仅模拟正在完成的一些工作量。谢谢。所以,如果我在
DoWor的
wait…
行中做实际工作
await Crawl("http://example.org/");