Using PLINQ to calculate and update values inside a cabinet does not work
I recently needed to do a total of reports. Where for each group I order lines and then calculate the running amount based on the previous lines within the group. Aha! I thought the perfect use case for PLINQ!
However, when I wrote the code, I got strange behavior. The values I changed showed up as changing when going through the debugger, but when they were available, they were always zero.
Sample code:
class Item
{
public int PortfolioID;
public int TAAccountID;
public DateTime TradeDate;
public decimal Shares;
public decimal RunningTotal;
}
List<Item> itemList = new List<Item>
{
new Item
{
PortfolioID = 1,
TAAccountID = 1,
TradeDate = new DateTime(2010, 5, 1),
Shares = 5.335m,
},
new Item
{
PortfolioID = 1,
TAAccountID = 1,
TradeDate = new DateTime(2010, 5, 2),
Shares = -2.335m,
},
new Item
{
PortfolioID = 2,
TAAccountID = 1,
TradeDate = new DateTime(2010, 5, 1),
Shares = 7.335m,
},
new Item
{
PortfolioID = 2,
TAAccountID = 1,
TradeDate = new DateTime(2010, 5, 2),
Shares = -3.335m,
},
};
var found = (from i in itemList
where i.TAAccountID == 1
select new Item
{
TAAccountID = i.TAAccountID,
PortfolioID = i.PortfolioID,
Shares = i.Shares,
TradeDate = i.TradeDate,
RunningTotal = 0
});
found.AsParallel().ForAll(x =>
{
var prevItems = found.Where(i => i.PortfolioID == x.PortfolioID
&& i.TAAccountID == x.TAAccountID
&& i.TradeDate <= x.TradeDate);
x.RunningTotal = prevItems.Sum(s => s.Shares);
});
foreach (Item i in found)
{
Console.WriteLine("Running total: {0}", i.RunningTotal);
}
Console.ReadLine();
If I change select to found as .ToArray()
, then it works fine and I get the calculated repeats.
Any ideas what I am doing wrong?
a source to share
When your PLINQ query is executed, "found" is IEnumerable<T>
not fully executed. Because LINQ to Objects uses deferred execution by default, each Found item will not be created until the PLINQ query reaches that position.
Because the ForAll method performs internal discovery, it receives a non-executable or only partially enumerable sequence. By adding .ToArray () (or ToList - basically anything that forces the LINQ to Objects query to execute) prior to your calling .ForAll, you force the LINQ to Objects query to execute, which allows the PLINQ query to execute as expected.
a source to share