[英]Tasks/Thread/Delegate.beginInvoke with Thread.Sleep()
[英]Thread.Sleep blocking parallel execution of tasks
我正在調用一個調用數據庫的worker方法,然后迭代並生成並行處理的返回值。 為了防止它錘擊數據庫,我在那里有一個Thread.Sleep來暫停執行到DB。 但是,這似乎是在Parallel.ForEach中仍然發生的阻塞執行。 實現這一目標以防止阻塞的最佳方法是什么?
private void ProcessWorkItems()
{
_cancellation = new CancellationTokenSource();
_cancellation.Token.Register(() => WorkItemRepository.ResetAbandonedWorkItems());
Task.Factory.StartNew(() =>
Parallel.ForEach(GetWorkItems().AsParallel().WithDegreeOfParallelism(10), workItem =>
{
var x = ItemFactory(workItem);
x.doWork();
}), _cancellation.Token);
}
private IEnumerable<IAnalysisServiceWorkItem> GetWorkItems()
{
while (!_cancellation.IsCancellationRequested)
{
var workItems = WorkItemRepository.GetItemList(); //database call
workItems.ForEach(item =>
{
item.QueueWorkItem(WorkItemRepository);
});
foreach (var item in workItems)
{
yield return item;
}
if (workItems.Count == 0)
{
Thread.Sleep(30000); //sleep this thread for 30 seconds if no work items.
}
}
yield break;
}
編輯:我改變它以包括答案,它仍然沒有按我期望的那樣工作。 我將.AsParallel()。WithDegreeOfParallelism(10)添加到GetWorkItems()調用中。 當我認為即使基本線程正在睡眠時Parallel仍應繼續執行,我的期望是否正確?
示例:我有15個項目,它迭代並抓取10個項目並啟動它們。 當每個人完成時,它會從GetWorkItems請求另一個,直到它試圖要求第16個項目。 此時它應該停止嘗試獲取更多項目,但應繼續處理項目11-15,直到完成。 是應該如何並行工作? 因為它目前沒有這樣做。 它目前正在做的是當它完成6時,它會鎖定后續的10個仍然在Parallel.ForEach中運行。
我建議您創建一個工作項的BlockingCollection (隊列),以及一個每隔30秒調用一次數據庫的計時器來填充它。 就像是:
BlockingCollection<WorkItem> WorkItems = new BlockingCollection<WorkItem>();
並在初始化:
System.Threading.Timer WorkItemTimer = new Timer((s) =>
{
var items = WorkItemRepository.GetItemList(); //database call
foreach (var item in items)
{
WorkItems.Add(item);
}
}, null, 30000, 30000);
這將每隔30秒向數據庫查詢一次。
要安排要處理的工作項,您可以使用許多不同的解決方案。 最接近你的是這樣的:
WorkItem item;
while (WorkItems.TryTake(out item, Timeout.Infinite, _cancellation))
{
Task.Factory.StartNew((s) =>
{
var myItem = (WorkItem)s;
// process here
}, item);
}
這消除了任何線程中的阻塞,並讓TPL決定如何最好地分配並行任務。
編輯:
實際上,更接近你所擁有的是:
foreach (var item in WorkItems.GetConsumingEnumerable(_cancellation))
{
// start task to process item
}
您可以使用:
Parallel.Foreach(WorkItems.GetConsumingEnumerable(_cancellation).AsParallel ...
我不知道這是否有用或有多好。 也許值得嘗試一下 。 。 。
編輯結束
一般來說,我建議你將其視為生產者/消費者應用程序,生產者是定期查詢數據庫以獲取新項目的線程。 我的示例每隔N(本例中為30)秒查詢數據庫一次,如果平均每30秒就可以清空一次工作隊列,這將很有效。 從項目發布到數據庫到結果之前,這將給出不到一分鍾的平均延遲。
您可以減少輪詢頻率(以及延遲),但這會導致更多的數據庫流量。
你也可以用它來獲得更好的體驗。 例如,如果您在30秒后輪詢數據庫並獲得大量項目,那么很可能您將很快獲得更多,並且您將需要在15秒(或更短)內再次輪詢。 相反,如果您在30秒后輪詢數據庫並且什么也得不到,那么您可以在再次輪詢之前等待更長時間。
您可以使用一次性計時器設置這種自適應輪詢。 也就是說,在創建計時器時為最后一個參數指定-1,這會導致它僅觸發一次。 您的計時器回調計算出在下次輪詢之前等待的時間,並調用Timer.Change
以使用新值初始化計時器。
您可以使用.WithDegreeOfParallelism()擴展方法強制PLinq同時運行任務。 在C#Threading Handbook的Call Blocking或I / O Intensive部分有一個很好的例子
你可能會對分區者犯規。
因為你傳遞的是IEnumerable,所以Parallel.ForEach將使用一個Chunk Partitioner,它可以嘗試一次從一個塊中的枚舉中獲取一些元素。 但你的IEnumerable.MoveNext可以睡覺,這會讓事情變得煩亂。
您可以編寫自己的分區程序,一次返回一個元素,但無論如何,我認為像Jim Mischel的建議這樣的生產者/消費者方法會更好。
你在睡覺時想要完成什么? 據我所知,你試圖避免敲擊數據庫調用。 我不知道有更好的方法可以做到這一點,但理想情況下,在數據可用於處理之前,您的GetItemList
調用似乎會阻塞。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.