父任务未等待子任务
本文关键字:子任务 等待 任务 | 更新日期: 2023-09-27 18:26:09
首先,我对线程很陌生。我试图实现的是,有一个url列表,我试图抓取(我想)并检测flash(如果url有),这太平行了。有些url有外部链接,这意味着我必须生成一个新任务并递归调用我的方法。我想使用AsParallel或Parallel.ForEach,但它们不接受Task输入。
所以我有两个问题。1) 我想要一种更好的方法来并行抓取url(如果需要,可以递归)2) 如果我写的是一个好的方法,那么我应该怎么做才能让任务等待它的孩子?
附言:我找过斯塔克弗流,找不到我需要的东西。如果我错过了已经有人回答了与我类似的问题,我很抱歉。
async Task CrawlAndDetectFlash(LearningResource resource, string url, int depth)
{
using (var client = new HttpClient())
using (var response = await client.GetAsync(url))
{
response.EnsureSuccessStatusCode();
using (var content = response.Content)
{
var result = content.ReadAsStringAsync().Result;
resource.FlashRequired = result.Contains("application/x-shockwave-flash") || result.Contains("application/x-director") || result.Contains(".swf") ? 1 : 0;
if (resource.FlashRequired == 0 && depth == 1)
{
var document = new HtmlDocument();
document.LoadHtml(result);
var links = document.DocumentNode.Descendants("a")
.Where(a => a.Attributes.Contains("class") && String.Equals(a.GetAttributeValue("class", string.Empty), "external"))
.Select(a => a.GetAttributeValue("href", null))
.Distinct()
.Where(u => !String.IsNullOrEmpty(u))
.ToList();
if (links.Count > 0)
{
foreach (var link in links)
{
Task child = CrawlAndDetectFlash(resource, link, 2);
child.Wait();
}
}
}
}
}
}
首先,您需要区分"并发"、"并行"answers"异步"。并发是指一次做不止一件事;并行是一种使用多个线程的并发形式;异步是一种没有线程的并发形式。当您希望将线程分布在多个CPU核心上时,并行性最适合CPU绑定的代码。当您不想阻塞线程时,异步最适合I/O绑定代码。
在您的情况下,看起来您主要是I/O绑定的,因此异步是可行的。这意味着AsParallel
和Parallel.ForEach
对于这个问题是不合适的解决方案(它们是并行的,而不是异步的)。
下一个教训(正如我在博客上所描述的)是,您不想阻塞异步代码;阻塞会破坏异步的整个点。因此不应使用Task<T>.Result
和Task.Wait
。不用这些,只需使用await
:
async Task CrawlAndDetectFlashAsync(LearningResource resource, string url, int depth)
{
using (var client = new HttpClient())
using (var response = await client.GetAsync(url))
{
response.EnsureSuccessStatusCode();
using (var content = response.Content)
{
var result = await content.ReadAsStringAsync(); // Result -> await
resource.FlashRequired = result.Contains("application/x-shockwave-flash") || result.Contains("application/x-director") || result.Contains(".swf") ? 1 : 0;
if (resource.FlashRequired == 0 && depth == 1)
{
var document = new HtmlDocument();
document.LoadHtml(result);
var links = document.DocumentNode.Descendants("a")
.Where(a => a.Attributes.Contains("class") && String.Equals(a.GetAttributeValue("class", string.Empty), "external"))
.Select(a => a.GetAttributeValue("href", null))
.Distinct()
.Where(u => !String.IsNullOrEmpty(u))
.ToList();
if (links.Count > 0)
{
foreach (var link in links)
{
Task child = CrawlAndDetectFlashAsync(resource, link, 2);
await child; // Wait -> await
}
}
}
}
}
}
既然该方法是适当异步的,那么您可以考虑添加更多的并发性。例如,如果您想同时处理所有子链接,foreach
循环可以重写为:
if (links.Count > 0)
{
var childTasks = links.Select(x => CrawlAndDetectFlashAsync(resource, x, 2)).ToList();
await Task.WhenAll(childTasks);
}
您可以像这样修改您的代码
List<Task> children = new List<Task>();
foreach (var link in links)
{
Task child = CrawlAndDetectFlash(resource, link, 2);
children.Add(child);
}
Task.WaitAll(children.ToArray());