2014-05-23 39 views
0

我目前正在尝试通过大小约1.5GB的文本文件进行循环,然后使用从中抓取的URL来从网站上拉下html。关于处理巨型文本文件和处理URL的建议

对于速度我试图处理所有的HTTP请求在一个新的线程,但由于C#不是我最强的语言,但对我在做什么的要求我有点困惑良好的线程实践。

这是我如何处理列表

private static void Main() 
    { 
     const Int32 BufferSize = 128; 
     using (var fileStream = File.OpenRead("dump.txt")) 
     using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) 
     { 
      String line; 
      var progress = 0; 

      while ((line = streamReader.ReadLine()) != null) 
      { 
       var stuff = line.Split('|'); 
       getHTML(stuff[3]); 

       progress += 1; 
       Console.WriteLine(progress); 
      } 
     } 
    } 

而且我拉下HTML作为这样

private static void getHTML(String url) 
    { 
     new Thread(() => 
     { 
      var client = new DecompressGzipResponse(); 
      var html = client.DownloadString(url); 

     }).Start(); 
    } 

虽然速度快做这个最初,经过约20000他们放慢速度,最终在32万之后应用程序将挂起并崩溃。当C#线程在函数完成时被终止了,我感到印象深刻?

任何人都可以提供任何示例/建议如何更好地做到这一点?

+0

20k线程在任何语言中都是一个坏主意。你不能只创建更多的线程并获得性能的线性增益,这不是CPU的工作方式。此外,你的线程不会产生任何东西。他们只是下载一个字符串,该字符串因为是本地函数而被立即丢弃。您需要以合理的大小批量处理这些请求。 –

+0

您可能希望考虑限制活动线程的数量。你可以阅读一个文件并且创建新的线程比完成它们要快得多。 – Blorgbeard

+0

@EdS。出于测试的目的,我并没有返回字符串,只是简单地看到了下载html的请求的速度有多快,而且没有线程的不合理的缓慢,通过创建线程我能够每秒处理几千个线程;然而。正如我在我的初始文章中所说的,我认为他们会在功能完成后摧毁自己。你有没有关于如何解决这个问题的代码示例? – user3037561

回答

0

您需要线程管理。

我的建议是使用Tasks而不是创建自己的主题。

通过使用任务并行库,您可以让运行时处理线程管理。默认情况下,它将在ThreadPool的线程上分配任务,并允许一定的并发级别,这取决于您拥有的CPU内核数量。它还会在现有线程可用时重用它们,而不是浪费时间创建新线程。

如果您想要获得更高级的功能,您可以创建自己的任务计划程序来自行管理计划方面。

又见What is difference between Task and Thread?

1

一个非常可靠的方法做,这是通过使用生产者 - 消费者模式。您创建一个线程安全的URL队列(例如,BlockingCollection<Uri>)。您的主线程是生产者,它将项目添加到队列中。然后您有多个使用者线程,每个线程都会从队列中读取Url并执行HTTP请求。见BlockingCollection

设置它并不是十分困难的:

BlockingCollection<Uri> UrlQueue = new BlockingCollection<Uri>(); 

// Main thread starts the consumer threads 
Task t1 = Task.Factory.StartNew(() => ProcessUrls, TaskCreationOptions.LongRunning); 
Task t2 = Task.Factory.StartNew(() => ProcessUrls, TaskCreationOptions.LongRunning); 
// create more tasks if you think necessary. 

// Now read your file 
foreach (var line in File.ReadLines(inputFileName)) 
{ 
    var theUri = ExtractUriFromLine(line); 
    UrlQueue.Add(theUri); 
} 

// when done adding lines to the queue, mark the queue as complete 
UrlQueue.CompleteAdding(); 

// now wait for the tasks to complete. 
t1.Wait(); 
t2.Wait(); 
// You could also use Task.WaitAll if you have an array of tasks 

各个线程处理的URL用这种方法:

void ProcessUrls() 
{ 
    foreach (var uri in UrlQueue.GetConsumingEnumerable()) 
    { 
     // code here to do a web request on that url 
    } 
} 

这是一个简单和可靠的方式做事情,但它不是特别快速。通过使用异步请求的第二个WebCient对象队列可以做得更好。例如,假设您想要有15个异步请求。您以BlockingCollection开始的方式相同,但您只有一个持久消费者线程。

const int MaxRequests = 15; 
BlockingCollection<WebClient> Clients = new BlockingCollection<WebClient>(); 

// start a single consumer thread 
var ProcessingThread = Task.Factory.StartNew(() => ProcessUrls, TaskCreationOptions.LongRunning); 

// Create the WebClient objects and add them to the queue 
for (var i = 0; i < MaxRequests; ++i) 
{ 
    var client = new WebClient(); 
    // Add an event handler for the DownloadDataCompleted event 
    client.DownloadDataCompleted += DownloadDataCompletedHandler; 
    // And add this client to the queue 
    Clients.Add(client); 
} 

// add the code from above that reads the file and populates the queue 

你的处理功能有所不同:

void ProcessUrls() 
{ 
    foreach (var uri in UrlQueue.GetConsumingEnumerable()) 
    { 
     // Wait for an available client 
     var client = Clients.Take(); 
     // and make an asynchronous request 
     client.DownloadDataAsync(uri, client); 
    } 
    // When the queue is empty, you need to wait for all of the 
    // clients to complete their requests. 
    // You know they're all done when you dequeue all of them. 
    for (int i = 0; i < MaxRequests; ++i) 
    { 
     var client = Clients.Take(); 
     client.Dispose(); 
    } 
} 

DownloadDataCompleted事件处理程序做一些事已下载数据,然后将此WebClient实例回馈客户的队列中。

void DownloadDataCompleteHandler(Object sender, DownloadDataCompletedEventArgs e) 
{ 
    // The data downloaded is in e.Result 
    // be sure to check the e.Error and e.Cancelled values to determine if an error occurred 

    // do something with the data 

    // And then add the client back to the queue 
    WebClient client = (WebClient)e.UserState; 
    Clients.Add(client); 
} 

这应该让你继续与15个并发请求,这是所有你可以做,而不会有一点复杂。您的系统可能会处理更多的并发请求,但WebClient启动异步请求的方式需要事先进行一些同步工作,并且该开销会使您可以处理的最大数量达到15。

可能能够有多个线程发起异步请求。在这种情况下,您可能拥有与处理器内核一样多的线程。所以在四核机器上,您可以拥有主线程和三个使用者线程。有了三个消费者线程,该技术可以为您提供45个并发请求。我不是某些,它可以很好地扩展,但它可能是值得一试。

有几种方法可以有数百个并发请求,但实现起来要复杂得多。