我的情况是不断生成新任务并将其添加到ConcurrentBag<Tasks>
。
我需要等待所有任务完成。
通过ConcurrentBag
等待WaitAll
中的所有任务是不够的,因为在前一次等待完成时任务数量会增加。
目前我正在以下列方式等待:
private void WaitAllTasks()
{
while (true)
{
int countAtStart = _tasks.Count();
Task.WaitAll(_tasks.ToArray());
int countAtEnd = _tasks.Count();
if (countAtStart == countAtEnd)
{
break;
}
#if DEBUG
if (_tasks.Count() > 100)
{
tokenSource.Cancel();
break;
}
#endif
}
}
我对while(true)
解决方案不满意。
任何人都可以提出一种更有效的方法来做到这一点(无需使用while(true)
不断集中处理器)
评论中要求的其他上下文信息。我不认为这与这个问题有关。
这段代码用于网络爬虫。爬网程序扫描页面内容并查找两种类型的信息。数据页面和链接页面。将扫描数据页并收集数据,扫描链接页面并从中收集更多链接。
由于每项任务都会进行活动并找到更多链接,因此他们会将链接添加到EventList
。列表中有一个事件OnAdd
(下面的代码),用于触发其他任务扫描新添加的URL。等等。
当没有更多正在运行的任务时,作业完成(因此不再添加链接)并且所有项目都已处理完毕。
public IEventList<ISearchStatus> CurrentLinks { get; private set; }
public IEventList<IDataStatus> CurrentData { get; private set; }
public IEventList<System.Dynamic.ExpandoObject> ResultData { get; set; }
private readonly ConcurrentBag<Task> _tasks = new ConcurrentBag<Task>();
private readonly CancellationTokenSource tokenSource = new CancellationTokenSource();
private readonly CancellationToken token;
public void Search(ISearchDefinition search)
{
CurrentLinks.OnAdd += UrlAdded;
CurrentData.OnAdd += DataUrlAdded;
var status = new SearchStatus(search);
CurrentLinks.Add(status);
WaitAllTasks();
_exporter.Export(ResultData as IList<System.Dynamic.ExpandoObject>);
}
private void DataUrlAdded(object o, EventArgs e)
{
var item = o as IDataStatus;
if (item == null)
{
return;
}
_tasks.Add(Task.Factory.StartNew(() => ProcessObjectSearch(item), token));
}
private void UrlAdded(object o, EventArgs e)
{
var item = o as ISearchStatus;
if (item==null)
{
return;
}
_tasks.Add(Task.Factory.StartNew(() => ProcessFollow(item), token));
_tasks.Add(Task.Factory.StartNew(() => ProcessData(item), token));
}
public class EventList<T> : List<T>, IEventList<T>
{
public EventHandler OnAdd { get; set; }
private readonly object locker = new object();
public new void Add(T item)
{
//lock (locker)
{
base.Add(item);
}
OnAdd?.Invoke(item, null);
}
public new bool Contains(T item)
{
//lock (locker)
{
return base.Contains(item);
}
}
}
答案 0 :(得分:0)
为什么不编写一个在创建任务时根据需要生成任务的函数?这样你就可以使用Task.WhenAll
等待它们完成,或者我错过了这一点? See this working here
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
try
{
Task.WhenAll(GetLazilyGeneratedSequenceOfTasks()).Wait();
Console.WriteLine("Fisnished.");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
public static IEnumerable<Task> GetLazilyGeneratedSequenceOfTasks()
{
var random = new Random();
var finished = false;
while (!finished)
{
var n = random.Next(1, 2001);
if (n < 50)
{
finished = true;
}
if (n > 499)
{
yield return Task.Delay(n);
}
Task.Delay(20).Wait();
}
yield break;
}
}
或者,如果您的问题不像我的回答所暗示的那样微不足道,我会考虑使用TPL Dataflow的网格。 BufferBlock
和ActionBlock
的组合可以让您非常接近您的需求。你可以start here。
无论哪种方式,我都建议您添加一项接受CancellationToken
或两项的规定。
答案 1 :(得分:0)
我认为可以使用基本设置的TPL Dataflow
库来完成此任务。您需要TransformManyBlock<Task, IEnumerable<DataTask>>
和ActionBlock
(可能更多)进行实际数据处理,如下所示:
// queue for a new urls to parse
var buffer = new BufferBlock<ParseTask>();
// parser itself, returns many data tasks from one url
// similar to LINQ.SelectMany method
var transform = new TransformManyBlock<ParseTask, DataTask>(task =>
{
// get all the additional urls to parse
var parsedLinks = GetLinkTasks(task);
// get all the data to parse
var parsedData = GetDataTasks(task);
// setup additional links to be parsed
foreach (var parsedLink in parsedLinks)
{
buffer.Post(parsedLink);
}
// return all the data to be processed
return parsedData;
});
// actual data processing
var consumer = new ActionBlock<DataTask>(s => ProcessData(s));
之后你需要链接每个之间的块:
buffer.LinkTo(transform, new DataflowLinkOptions { PropagateCompletion = true });
transform.LinkTo(consumer, new DataflowLinkOptions { PropagateCompletion = true });
现在你有一个很好的管道,它将在后台执行。目前您意识到所需的一切都已解析,您只需调用Complete
方法获取一个块,以便它停止接受新闻消息。在buffer
变空之后,它会将管道中的完成传播到transform
块,这会将其传播给消费者,您需要等待Completion
任务:
// no additional links would be accepted
buffer.Complete();
// after all the tasks are done, this will get fired
await consumer.Completion;
您可以查看完成时间,例如,如果buffer
'Count
属性和 transform
'InputCount
和 transform
'CurrentDegreeOfParallelism
(这是TransformManyBlock
的内部属性)等于0
。
但是,我建议你在这里实现一些额外的逻辑来确定当前的变压器数量,因为使用内部逻辑不是一个很好的解决方案。至于取消管道,您可以使用TPL
创建一个CancellationToken
块,或者为所有块创建一个块,或者为每个块创建一个专用块,以便取消开箱即用。