![](/img/trans.png)
[英]C# Parallel.Foreach int[] in List<T> not changed properly
[英]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上安排工作,而不是分割結果。
但是,我想指出您有一些次要的代碼問題:
您可以在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)));
這將幫助您避免不必要的分組數據副本
您可以使用var
關鍵字來避免代碼中類型的重復(這只是示例行,可以在代碼中多次更改):
foreach (var group in groups)
除非您知道自己在做什么(並且我認為不是),否則不應該使用最大並行度:
var options = new ParallelOptions { MaxDegreeOfParallelism = Parameters.MaxDegreeOfParallelism };
TPL
默認任務計划程序會嘗試調整任務的線程池和CPU使用率,因此通常此數字應等於Environment.ProcessorCount
。
您可以將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.