我正在建造一个刮刀。我的目标是启动X浏览器(其中X是线程数)并继续通过在X部分中拆分该列表来抓取每个URL的URL列表。
我决定使用3个线程(3个浏览器)和10个URL列表。
问题:如何在浏览器之间分隔每个任务:
Browser1从0到3
Browser2将列表中的项目从4到7
Browser3从8到10
所有浏览器都应该同时抓取传递的URL列表。
我已经拥有此BlockingCollection
:
BlockingCollection<Action> _taskQ = new BlockingCollection<Action>();
public Multithreading(int workerCount)
{
// Create and start a separate Task for each consumer:
for (int i = 0; i < workerCount; i++)
Task.Factory.StartNew(Consume);
}
public void Dispose() { _taskQ.CompleteAdding(); }
public void EnqueueTask(Action action) { _taskQ.Add(action); }
void Consume()
{
// This sequence that we’re enumerating will block when no elements
// are available and will end when CompleteAdding is called.
foreach (Action action in _taskQ.GetConsumingEnumerable())
action(); // Perform task.
}
public int ItemsCount()
{
return _taskQ.Count;
}
可以像这样使用:
Multithreading multithread = new Multithreading(3); //3 threads
foreach(string url in urlList){
multithread.EnqueueTask(new Action(() =>
{
startScraping(browser1); //or browser2 or browser3
}));
}
我需要在抓取之前创建浏览器实例,因为我不想为每个线程启动一个新的浏览器。
答案 0 :(得分:1)
考虑到Henk Holtermans可能需要最大速度的评论,即尽可能保持浏览器的忙碌,请使用:
private static void StartScraping(int id, IEnumerable<Uri> urls)
{
// Construct browser here
foreach (Uri url in urls)
{
// Use browser to process url here
Console.WriteLine("Browser {0} is processing url {1}", id, url);
}
}
在main中:
int nrWorkers = 3;
int nrUrls = 10;
BlockingCollection<Uri> taskQ = new BlockingCollection<Uri>();
foreach (int i in Enumerable.Range(0, nrWorkers))
{
Task.Run(() => StartScraping(i, taskQ.GetConsumingEnumerable()));
}
foreach (int i in Enumerable.Range(0, nrUrls))
{
taskQ.Add(new Uri(String.Format("http://Url{0}", i)));
}
taskQ.CompleteAdding();
答案 1 :(得分:1)
我认为通常的方法是拥有一个阻塞队列,一个提供者线程和一个任意的工作池。
提供程序线程负责将URL添加到队列。当没有人可以添加时,它会阻止。
工作线程实例化浏览器,然后从队列中检索单个URL,对其进行擦除,然后循环返回以获取更多信息。它在队列为空时阻塞。
你可以随心所欲地开始工作,他们只是在他们之间进行整理。
主线开始所有线程并退出边线。如果有UI,它会管理UI。
多线程可能很难调试。您可能希望至少部分工作使用“任务”。
答案 2 :(得分:0)
您可以为任务和工作人员提供一些Id
。然后,您将BlockingCollection[]
而不仅仅是BlockingCollection
。每个消费者都会从数组中使用自己的BlockingCollection
。我们的工作是找到合适的消费者并发布工作。
BlockingCollection<Action>[] _taskQ;
private int taskCounter = -1;
public Multithreading(int workerCount)
{
_taskQ = new BlockingCollection<Action>[workerCount];
for (int i = 0; i < workerCount; i++)
{
int workerId = i;//To avoid closure issue
_taskQ[workerId] = new BlockingCollection<Action>();
Task.Factory.StartNew(()=> Consume(workerId));
}
}
public void EnqueueTask(Action action)
{
int value = Interlocked.Increment(ref taskCounter);
int index = value / 4;//Your own logic to find the index here
_taskQ[index].Add(action);
}
void Consume(int workerId)
{
foreach (Action action in _taskQ[workerId].GetConsumingEnumerable())
action();// Perform task.
}
答案 3 :(得分:0)
使用后台工作程序的简单解决方案可以限制线程数:
public class Scraper : IDisposable
{
private readonly BlockingCollection<Action> tasks;
private readonly IList<BackgroundWorker> workers;
public Scraper(IList<Uri> urls, int numberOfThreads)
{
for (var i = 0; i < urls.Count; i++)
{
var url = urls[i];
tasks.Add(() => Scrape(url));
}
for (var i = 0; i < numberOfThreads; i++)
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
Action task;
while (tasks.TryTake(out task))
{
task();
}
};
workers.Add(worker);
worker.RunWorkerAsync();
}
}
public void Scrape(Uri url)
{
Console.WriteLine("Scraping url {0}", url);
}
public void Dispose()
{
throw new NotImplementedException();
}
}