多个Web请求的最佳多线程方法

时间:2013-03-29 15:21:47

标签: c# multithreading httpwebrequest multitasking

我想创建一个程序来抓取并检查我的网站是否存在http错误和其他内容。 我想用多个线程来做这件事,这些线程应该接受像抓取的url这样的参数。 虽然我希望X线程处于活动状态,但是已经有Y任务等待执行。

现在我想知道执行此操作的最佳策略是什么:ThreadPool,Tasks,Threads甚至是其他什么?

4 个答案:

答案 0 :(得分:5)

这是一个示例,显示如何排队一堆任务但限制同时运行的数量。它使用Queue来跟踪准备运行的任务,并使用Dictionary来跟踪正在运行的任务。任务完成后,它会调用一个回调方法将自己从Dictionary中删除。当空间可用时,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);
        }
    }
}

答案 1 :(得分:2)

我建议使用(异步)Task来下载数据然后处理(在线程池上)。

我建议您限制每个目标服务器的请求数量,而不是限制任务。好消息:.NET already does this for you

这使您的代码简单如下:

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.
}

你可以用一个简单的开始:

await Crawl("http://example.org/");

答案 2 :(得分:0)

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

“线程池将为频繁和相对较短的操作带来好处 重用已经创建的线程而不是创建新线程(一个昂贵的过程) 当有新的工作项请求时,限制了线程创建的速度(我相信这只是在.NET 3.5中)

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

如果您的工作负载始终有大量的线程池请求进入,那么线程池将通过上述过程在池中创建更多线程来调整自身以适应您的工作负载,以便有更多的线程可供处理请求“

Thread vs ThreadPool

答案 3 :(得分:-1)

好吧,Task是一个很好的方法,因为这意味着你不必担心编写很多“管道”代码。

我建议您查看Joe Albahari的线程网站,这是一个很好的线程入门:

http://www.albahari.com/threading/