[英]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.