簡體   English   中英

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 HandbookCall Blocking或I / O Intensive部分有一個很好的例子

你可能會對分區者犯規。

因為你傳遞的是IEnumerable,所以Parallel.ForEach將使用一個Chunk Partitioner,它可以嘗試一次從一個塊中的枚舉中獲取一些元素。 但你的IEnumerable.MoveNext可以睡覺,這會讓事情變得煩亂。

您可以編寫自己的分區程序,一次返回一個元素,但無論如何,我認為像Jim Mischel的建議這樣的生產者/消費者方法會更好。

你在睡覺時想要完成什么? 據我所知,你試圖避免敲擊數據庫調用。 我不知道有更好的方法可以做到這一點,但理想情況下,在數據可用於處理之前,您的GetItemList調用似乎會阻塞。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM