我需要抓取父网页及其子网页,我遵循http://www.albahari.com/threading/part4.aspx#%5FWait%5Fand%5FPulse中的生产者/消费者概念。另外,我使用5个线程将链接排队并出列。
如果队列长度未知,那么在所有线程完成所有线程处理后,如何结束/加入所有线程的任何建议?
以下是关于我如何编码的想法。
static void Main(string[] args)
{
//enqueue parent links here
...
//then start crawling via threading
...
}
public void Crawl()
{
//dequeue
//get child links
//enqueue child links
}
答案 0 :(得分:3)
如果所有线程都空闲(即在队列中等待)并且队列为空,那么就完成了。
处理这种情况的一种简单方法是让线程在尝试访问队列时使用超时。像BlockingCollection.TryTake这样的东西。每当TryTake
超时时,线程就会更新一个字段,说明它已经闲置了多长时间:
while (!queue.TryTake(out item, 5000, token))
{
if (token.IsCancellationRequested)
break;
// here, update idle counter
}
然后,您可以拥有一个每15秒执行一次的计时器来检查所有线程的空闲计数器。如果所有线程都空闲了一段时间(可能是一分钟),那么计时器可以设置取消令牌。这将杀死所有线程。您的主程序也可以监控取消令牌。
顺便提一句,您可以在没有BlockingCollection
的情况下执行此操作并取消。您只需要创建自己的取消信号机制,如果您在队列上使用锁,则可以用Monitor.TryEnter
等替换锁定语法。
还有其他几种方法可以解决这个问题,尽管它们需要对您的程序进行一些重大改组。
答案 1 :(得分:1)
您可以在最后将虚拟令牌排入队列,并在遇到此令牌时让线程退出。像:
public void Crawl()
{
int report = 0;
while(true)
{
if(!(queue.Count == 0))
{
if(report > 0) Interlocked.Decrement(ref report);
//dequeue
if(token == "TERMINATION")
return;
else
//enqueue child links
}
else
{
if(report == num_threads) // all threads have signaled empty queue
queue.Enqueue("TERMINATION");
else
Interlocked.Increment(ref report); // this thread has found the queue empty
}
}
}
当然,我省略了enqueue/dequeue
操作的锁定。
答案 2 :(得分:0)
线程可以发出信号,表示已经结束了他们的工作,例如举起一个事件,或者调用一个代表。
static void Main(string[] args)
{
//enqueue parent links here
...
//then start crawling via threading
...
}
public void X()
{
//block the threads until all of them are here
}
public void Crawl(Action x)
{
//dequeue
//get child links
//enqueue child links
//call x()
}
答案 3 :(得分:0)
如果您愿意使用Task Parallel Library,则无需手动处理生产者 - 消费者资料。使用AttachToParent
选项创建任务时,子任务将以父任务链接的方式在子任务完成之前不会完成。
class Program
{
static void Main(string[] args)
{
var task = CrawlAsync("http://stackoverflow.com");
task.Wait();
}
static Task CrawlAsync(string url)
{
return Task.Factory.StartNew(
() =>
{
string[] children = ExtractChildren(url);
foreach (string child in children)
{
CrawlAsync(child);
}
ProcessUrl(url);
}, TaskCreationOptions.AttachedToParent);
}
static string[] ExtractChildren(string root)
{
// Return all child urls here.
}
static void ProcessUrl(string url)
{
// Process the url here.
}
}
您可以使用Parallel.ForEach
删除一些显式任务创建逻辑。