【问题标题】:Best multi-thread approach for multiple web requests多个 Web 请求的最佳多线程方法
【发布时间】:2013-03-20 07:50:14
【问题描述】:

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

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

【问题讨论】:

  • “最佳”很难定义。我建议您研究“相关”问题(右下方),然后选择您认为最适合您的应用程序的问题。可能任务是要走的路,但这仍然留下了很大的变化空间。

标签: c# multithreading httpwebrequest multitasking


【解决方案1】:

这是一个示例,展示了如何将一堆任务排队,但限制同时运行的数量。它使用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);
        }
    }
}

【讨论】:

  • 嗨,for 循环与Rnd.. 的目的是什么?这个 => for (int i = 0; i &lt; Rnd.Next(20); i++) { if (token.IsCancellationRequested) break; }
  • @Shiva - 循环只是模拟正在完成的一些工作。
  • 谢谢。因此,如果我在DoWork 方法的await... 行中进行实际工作,那么我将删除这个for 循环。在这种情况下,我会使用if (token.IsCancellationRequested) return; 代替if (token.IsCancellationRequested) break; 吗?
【解决方案2】:

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

我建议您限制每个目标服务器的请求数,而不是限制任务。好消息:.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/");

【讨论】:

    【解决方案3】:

    我建议使用线程池。 Is 很容易使用,因为它有几个好处:

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

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

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

    Thread vs ThreadPool

    【讨论】:

      【解决方案4】:

      嗯,Task 是一个不错的选择,因为这意味着您不必担心编写大量“管道”代码。

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

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

      【讨论】:

        猜你喜欢
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 2018-10-13
        • 1970-01-01
        • 2021-02-25
        • 2018-02-24
        • 2016-12-21
        • 1970-01-01
        相关资源
        最近更新 更多