[英]C# Multi-threading, wait for all task to complete in a situation when new tasks are being constantly added
我遇到的情况是不断生成新任务并将其添加到ConcurrentBag<Tasks>
。
我需要等待所有任务完成。
仅通过WaitAll
等待ConcurrentBag
中的所有任务是不够的,因为在前一次等待完成时,任务数量会增加。
目前,我正在按以下方式等待:
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)
不断地合并处理器)
注释中要求的其他上下文信息。 我认为这与问题无关。
这段代码在Web搜寻器中使用。 搜寻器扫描页面内容并查找两种类型的信息。 数据页和链接页。 将扫描数据页面并收集数据,将扫描链接页面并从中收集更多链接。
当每个任务进行活动并查找更多链接时,它们会将链接添加到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);
}
}
}
创建任务时,为什么不编写一个可以根据需要生成任务的函数呢? 这样,您可以只使用Task.WhenAll
等待它们完成,或者我错过了重点吗? 看到这里工作 。
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
的组合将使您非常接近所需的内容。 你可以从这里开始 。
无论哪种方式,我建议您都包含一个接受CancellationToken
或两个的规定。
我认为可以使用非常基本的设置使用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
。
但是,我建议您在此处实施一些其他逻辑来确定电流互感器的数量,因为使用内部逻辑并不是一个很好的解决方案。 至于取消管道,您可以创建一个带有CancellationToken
的TPL
块,该TPL
块可以全部CancellationToken
,也可以每个块专用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.