簡體   English   中英

C#拆分列表 <T> 使用TPL Parallel ForEach分組

[英]C# Split List<T> into groups using TPL Parallel ForEach

我需要處理成千上萬個元素的List<T>

首先,我需要按年份和類型對元素進行分組,因此獲得List<List<T>> 然后,對於每個內部List<T>我想添加類型T的對象,直到List<T>達到最大包大小,然后創建一個新包,並以相同的方式進行。

我想使用Parallel.ForEach循環。

如果我按順序運行它,我的實際實現效果很好,但是邏輯不是線程安全的,我想更改它。
我認為問題出在內部Parallel.ForEach循環上,當達到List<T>的最大大小時,我在同一引用內實例化了一個新的List<T>

private ConcurrentBag<ConcurrentBag<DumpDocument>> InitializePackages()
{
    // Group by Type and Year
    ConcurrentBag<ConcurrentBag<DumpDocument>> groups = new ConcurrentBag<ConcurrentBag<DumpDocument>>(Dump.DumpDocuments.GroupBy(d => new { d.Type, d.Year })
        .Select(g => new ConcurrentBag<DumpDocument> (g.ToList()))
        .ToList());

    // Documents lists with max package dimension
    ConcurrentBag<ConcurrentBag<DumpDocument>> documentGroups = new ConcurrentBag<ConcurrentBag<DumpDocument>>();

    foreach (ConcurrentBag<DumpDocument> group in groups)
    {       
        long currentPackageSize = 0;

        ConcurrentBag<DumpDocument> documentGroup = new ConcurrentBag<DumpDocument>();

        ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = Parameters.MaxDegreeOfParallelism };
        Parallel.ForEach(group, options, new Action<DumpDocument, ParallelLoopState>((DumpDocument document, ParallelLoopState state) =>
            {
                long currentDocumentSize = new FileInfo(document.FilePath).Length;

                // If MaxPackageSize = 0 then no splitting to apply and the process works well
                if (Parameters.MaxPackageSize > 0 && currentPackageSize + currentDocumentSize > Parameters.MaxPackageSize)
                {
                    documentGroups.Add(documentGroup);

                    // Here's the problem!
                    documentGroup = new ConcurrentBag<DumpDocument>();

                    currentPackageSize = 0;
                }

                documentGroup.Add(document);
                currentPackageSize += currentDocumentSize;
            }));

        if (documentGroup.Count > 0)
            documentGroups.Add(documentGroup);
    }

    return documentGroups;
}

public class DumpDocument
{
    public string Id { get; set; }
    public long Type { get; set; }
    public string MimeType { get; set; }
    public int Year { get; set; }
    public string FilePath { get; set; }
}

由於我的操作非常簡單,因此實際上我只需要使用以下方法獲取文件大小:

long currentDocumentSize = new FileInfo(document.FilePath).Length;

我了解到我也可以使用Partitioner ,但是我從未使用過它,無論如何現在這不是我的優先考慮。

我也已經讀過類似的問題 ,但不能解決內部循環問題。

更新28/12/2016

我更新了代碼以滿足驗證要求。

代碼更新后,您似乎正在使用ConcurrentBag因此代碼中還有另一個非線程安全的邏輯:

long currentPackageSize = 0;
if (// .. && 
    currentPackageSize + currentDocumentSize > Parameters.MaxPackageSize
// ...
{
    // ...
    currentPackageSize += currentDocumentSize;
}

+=運算符不是原子的,您肯定在那里有一個競爭條件,在這里讀取long變量的值不是線程安全的。 您可以在此處引入locks ,也可以使用Interlocked自動更新該值:

Interlocked.Add(ref currentPackageSize, currentDocumentSize);
Interlocked.Exchange(ref currentPackageSize, 0);
Interlocked.Read(ref currentPackageSize);

使用此類將導致一些重構代碼(我認為,在您的情況下,最好使用CAS操作(例如CompareExchange )),因此,也許對您而言,這是使用鎖的最簡單方法。 您可能應該同時實現這兩種方法並對其進行測試,並衡量執行時間。

而且,正如您所看到的,實例化也不是線程安全的,因此您必須鎖定變量(這將導致線程同步暫停)或將代碼重構為兩步:首先,您獲取了所有文件並行排列大小,然后按順序迭代結果,避免出現競爭情況。

至於Partitioner ,您不應該在這里使用此類,因為它通常用於在CPU上安排工作,而不是分割結果。

但是,我想指出您有一些次要的代碼問題:

  1. 您可以在ConcurrentBag的構造函數中刪除ToList()調用,因為它接受IEnumerable ,您已經擁有它:

     ConcurrentBag<ConcurrentBag<DumpDocument>> groups = new ConcurrentBag<ConcurrentBag<DumpDocument>>(Dump.DumpDocuments.GroupBy(d => new { d.Type, d.Year }) .Select(g => new ConcurrentBag<DumpDocument> (g))); 

    這將幫助您避免不必要的分組數據副本

  2. 您可以使用var關鍵字來避免代碼中類型的重復(這只是示例行,可以在代碼中多次更改):

     foreach (var group in groups) 
  3. 除非您知道自己在做什么(並且我認為不是),否則不應該使用最大並行度:

     var options = new ParallelOptions { MaxDegreeOfParallelism = Parameters.MaxDegreeOfParallelism }; 

    TPL默認任務計划程序會嘗試調整任務的線程池和CPU使用率,因此通常此數字應等於Environment.ProcessorCount

  4. 您可以將lambda語法用於Parallel.ForEach ,而不創建新的Action (也可以將以下代碼移到方法例程中):

     (document, state) => { long currentDocumentSize = new FileInfo(document.FilePath).Length; // If MaxPackageSize = 0 then no splitting to apply and the process works well if (Parameters.MaxPackageSize > 0 && currentPackageSize + currentDocumentSize > Parameters.MaxPackageSize) { documentGroups.Add(documentGroup); // Here's the problem! documentGroup = new ConcurrentBag<DumpDocument>(); currentPackageSize = 0; } documentGroup.Add(document); currentPackageSize += currentDocumentSize; } 

    因為您已經有一個通用集合(一個袋子),並且已經接受了ParallelLoopState作為第二個參數,所以lambda正確編譯。

暫無
暫無

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

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