一个非常可靠的方法做,这是通过使用生产者 - 消费者模式。您创建一个线程安全的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个并发请求。我不是某些,它可以很好地扩展,但它可能是值得一试。
有几种方法可以有数百个并发请求,但实现起来要复杂得多。
20k线程在任何语言中都是一个坏主意。你不能只创建更多的线程并获得性能的线性增益,这不是CPU的工作方式。此外,你的线程不会产生任何东西。他们只是下载一个字符串,该字符串因为是本地函数而被立即丢弃。您需要以合理的大小批量处理这些请求。 –
您可能希望考虑限制活动线程的数量。你可以阅读一个文件并且创建新的线程比完成它们要快得多。 – Blorgbeard
@EdS。出于测试的目的,我并没有返回字符串,只是简单地看到了下载html的请求的速度有多快,而且没有线程的不合理的缓慢,通过创建线程我能够每秒处理几千个线程;然而。正如我在我的初始文章中所说的,我认为他们会在功能完成后摧毁自己。你有没有关于如何解决这个问题的代码示例? – user3037561