Parallel.ForEach and DbContext - c#

I'm using Parallel.ForEach and it's hugely improving the performance of my code, but I'm curious about DbContext with multiple threads. I know it's not thread safe so I'm using locks where I need to.
The loop iterates over a dictionary and calculates statistics:
Dictionary<string, List<decimal>> decimalStats = new Dictionary<string, List<decimal>>(); // this gets populated in another irrelevant loop
List<ComparativeStatistic> comparativeStats = db.ComparativeStatistics.ToList();
var statLock = new object();
Parallel.ForEach(decimalStats, entry =>
{
List<decimal> vals = ((List<decimal>)entry.Value).ToList();
if (vals.Count > 0)
{
string[] ids = entry.Key.Split('#');
int questionId = int.Parse(ids[0]);
int yearId = int.Parse(ids[1]);
int adjacentYearId = int.Parse(ids[2]);
var stat = comparativeStats.Where(l => l.QuestionID == questionId && l.YearID == yearId && l.AdjacentYearID == adjacentYearId).FirstOrDefault();
if (stat == null)
{
stat = new ComparativeStatistic();
stat.QuestionnaireQuestionID = questionId;
stat.FinancialYearID = yearId;
stat.AdjacentFinancialYearID = adjacentYearId;
stat.CurrencyID = currencyId;
stat.IndustryID = industryId;
lock (statLock) { db.ComparativeStatistics.Add(stat); }
}
stat.TimeStamp = DateTime.Now;
decimal total = 0M;
decimal? mean = null;
foreach (var val in vals)
{
total += val;
}
mean = Decimal.Round((total / vals.Count), 2, MidpointRounding.AwayFromZero);
stat.Mean = mean;
}
});
db.SaveChanges();
My question: Why do I only need the lock when I'm adding something to the database? If stat is never null - if there's always already a database entry for it - I can run this loop without a lock with no problems, and the database gets updated as intended. If stat is null for a particular loop and I don't have the lock there, a System.AggregateException gets thrown.
edit1: I've tried opening a new connection to the database each time instead of using lock, which also works when adding to the database (identical to the loop above, I've added comments where it differs):
Parallel.ForEach(decimalStats, entry =>
{
List<decimal> vals = ((List<decimal>)entry.Value).ToList();
if (vals.Count > 0)
{
using (var dbThread = new PDBContext()) // new db connection
{
string[] ids = entry.Key.Split('#');
int questionId = int.Parse(ids[0]);
int yearId = int.Parse(ids[1]);
int adjacentYearId = int.Parse(ids[2]);
var stat = comparativeStats.Where(l => l.QuestionID == questionId && l.YearID == yearId && l.AdjacentYearID == adjacentYearId).FirstOrDefault();
if (stat == null)
{
stat = new ComparativeStatistic();
stat.QuestionnaireQuestionID = questionId;
stat.FinancialYearID = yearId;
stat.AdjacentFinancialYearID = adjacentYearId;
stat.CurrencyID = currencyId;
stat.IndustryID = industryId;
dbThread.ComparativeStatistics.Add(stat); // no need for a lock
}
stat.TimeStamp = DateTime.Now;
decimal total = 0M;
decimal? mean = null;
foreach (var val in vals)
{
total += val;
}
mean = Decimal.Round((total / vals.Count), 2, MidpointRounding.AwayFromZero);
stat.Mean = mean;
dbThread.SaveChanges(); // save
}
}
});
Is this safe to do? I'm sure Entity Framework's connection pooling is smart enough but I'm wondering if I should add any parameters to limit the number of threads/connections.

Related

How to convert nested for loop with if condition to .Net Linq?

I am working on a function which will modify the input payload properties. The payload contains nodes and each node contains list of features. I need to remove some specific features match condition and also modify each node window property start and end time. I have written the function using traditional nested for loop, but struggling to convert it to Linq function. Anyone has idea how to convert this nested for loop function to a Linq function?
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var nodes = payload.Nodes;
for (var i = 0; i < nodes.Count(); ++i)
{
var node = nodes[i];
var features = node.Features;
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
var windows = node.Windows;
for (var k = 0; k < windows.Count(); ++k)
{
var window = windows[k];
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}
Let's do it in parts. This first code of yours removes features that are in a list,
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
We need to convert that to "keep features not in a list"
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
This part of your code skips if function type is a certain kind, and zeroes timespans if the function type is not a certain kind:
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
Which might be better as a loop that operates on just those things we are interested in (we want to modify only those Windows where the nodefuctionTypeId is not Hours):
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = new TimeSpan.FromHous(startTime);
window.EndHour = new TimeSpan.FromHours(endTime);
}
Meaning the whole is:
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = TimeSpan.FromHous(startTime);
window.EndHour = TimeSpan.FromHours(endTime);
}
}
}
I don't think I'd convert it all to a Linq form, as it would make a mess; linq queries should not have side effects (modify the objects the query iterates over) so particularly the second part where the time spans are being zeroed would have to become an operation where everything about the window was being copied over to a new Window if you wanted to LINQ it.
If a window is literally just a time span pair then it's not so bad, or if you want to provide a constructor that takes an existing Window and a startTime and endTime:
public Window(Window copyFrom, int startTime, int endTime){
this.X = copyFrom.X;
this.Y = copyFrom.Y;
...
this.StartHour = TimeSpan.FromHours(strtTime);
this.EndHour = TimeSpan.FromHours(endTime);
}
Then maybe your method could become some linq:
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
node.Windows = node.Windows.Select(w => w.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS ? w : new Window(w, startTime, endTime).ToArray();
}
..but I don't know if I would try for a wholesale replacement of the entire nodes list, for reasons even based on the name of the method: ApplyTransformations sounds like it means "take this list of nodes and change bits of them" not "take this list of nodes and give me a new list with some or all of the nodes replaced or modified" - that sort of behavior in code could wreck something else, if the calling code is expecting a tweak and the object it sends (or objects within it) are swapped out for new ones
Using a linq query for the second part would make things messier, better to just have a for loop
Something like this should work:
var nodes = payload.Nodes;
nodes.Features = nodes.Features.Where(f => !(
f.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
);
foreach(var window in nodes.Windows)
{
if (window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS)
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
Pls have look at below code snippet to avoid nested loop using linq.
public class Employee
{
public string Name { get; set; }
public List<EmployeeProject> EmployeeProject { get; set; }
}
public class EmployeeProject
{
public string ProjectName { get; set; }
public string ClientName { get; set; }
}
/// <summary>
/// Object mapping
/// </summary>
/// <returns></returns>
public static List<Employee> CreateObject()
{
var employeeList = new List<Employee>();
var employee = new Employee();
var employeeProjectList = new List<EmployeeProject>();
employee.Name = "John";
var employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome";
employeeProject.ClientName = "Google";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp";
employeeProject.ClientName = "Meta";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
employee.Name = "Alex";
employeeProjectList = new List<EmployeeProject>();
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome2";
employeeProject.ClientName = "Google2";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp2";
employeeProject.ClientName = "Meta2";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
return employeeList;
}
/// <summary>
/// Linq function
/// </summary>
public static void LinqFunctionForNestedQuery()
{
var employeeObject = CreateObject();
var result1 = employeeObject.Select(x =>
{
x.EmployeeProject = x.EmployeeProject.Select(y =>
{
y.ProjectName.Contains("Chrome");
return y;
}).ToList();
return x;
});
}
To maintain readability of the code and to make it simpler , I have modified the code.
Step1 : Replace for with foreach if you can
Step2 : Replace foreach with linq
Important tip. Resharper helps you with code suggestions.
Solution
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
foreach (var node in payload.Nodes)
{
var features = node.Features;
foreach (var feature in features.Where(feature => feature.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D))
{
features.Remove(feature);
}
var windows = node.Windows;
foreach (var window in windows
.Where(window => window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS))
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}

Merging lists with a for loop

I'm working on an algorithm which can generate 2 types of recommendations, restaurants and dishes. All of this works fine, but I wanted to merge these 2 types of recommendations in a single list, which is where I encountered some issues. From my previous question I concluded that I needed a wrapper class, which I have set up like this:
public class RecommenderItem
{
public Guid Id { get; set; }
public object Entity { get; set; }
}
Now I want to alternate the 2 types of recommendations so the list would look like this:
[Restaurant][Dish][Restaurant][Dish][Restaurant][Dish] //Etc...
Note that these recommendations are completely separate. They are generated purely based on the user's preference, and they have no correlation in between them. My product owner wants to show these recommendations on the home page of our app like this.
These lists are different in length, so if I have added all items from a list, I wanted to just add the remaining objects from the other list. A possible scenario of this could look like this:
/*Other objects before this...*/[Dish][Restaurant][Dish][Dish][Dish] //Etc...
Here did the list of restaurant objects run out and I just wanted to add the remaining dish recommendations at the end of the list.
I have gotten this far, but I'm unsure how I would catch an IndexOutOfBounds exception and add the rest of the remaining objects at the end.
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations,
List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = 0;
//Check which list is longer and use that count
if (restaurantRecommendations.Count > dishRecommendations.Count)
count = dishRecommendations.Count;
else
count = restaurantRecommendations.Count;
for (int i = 0; i < count; i++)
{
//I'm fully aware this isn't the most optimal way of doing this,
//but I'm only looking at functionality here, optimizing performance comes later.
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
return output;
}
Does anyone have an idea how I could do this? Could I just catch an IndexOutOfBounds exception and use .AddRange() for the remaining objects? I'm not sure how I could check which list was out of bounds.
Let me know if I should elaborate more and thanks in advance!
Edit: -removed because it wasn't fair.-
This is a fairly succinct way of doing this.
While not Linq, it works in the spirit of the way Linq works by deferring doing any work until the resulting sequence is enumerated:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
using (var r = restaurants.GetEnumerator())
using (var d = dishes.GetEnumerator())
{
while (true)
{
bool rAvailable = r.MoveNext();
bool dAvailable = d.MoveNext();
if (rAvailable)
yield return new RecommenderItem { Id = r.Current.Id, Entity = r.Current };
if (dAvailable)
yield return new RecommenderItem { Id = d.Current.Id, Entity = d.Current };
if (!rAvailable && !dAvailable)
break;
}
}
}
If you happen to be using the MoreLinq NuGet package that includes the ZipLongest extension method, you can use the following simplified implementation instead:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
foreach (var item in restaurants.ZipLongest(dishes, (r, d) => new { r, d }))
{
if (item.r != null)
yield return new RecommenderItem { Id = item.r.Id, Entity = item.r };
if (item.d != null)
yield return new RecommenderItem { Id = item.d.Id, Entity = item.d };
}
}
Addendum
As #InBetween posted in his answer, you can put the interleaving logic into an extension method. Here's my version; it's substantially the same, except I've added a small optimisation to avoid calling .MoveNext() when its not necessary:
public static class EnumerableExt
{
public static IEnumerable<T> Interleave<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
using (var ae = a.GetEnumerator())
using (var be = b.GetEnumerator())
{
bool aAvailable = true;
bool bAvailable = true;
while (aAvailable || bAvailable)
{
aAvailable = aAvailable && ae.MoveNext();
bAvailable = bAvailable && be.MoveNext();
if (aAvailable)
yield return ae.Current;
if (bAvailable)
yield return be.Current;
}
}
}
}
Once you have that, I realised that you don't need to write an implict operator. Instead, you can just convert the two sequences to the resultant type before calling Interleave() like so:
var restaurantsAsRecommenderItems =
restaurantRecommendations
.Select(r => new RecommenderItem {Id = r.Id, Entity = r});
var dishesAsRecommenderItems =
dishRecommendations
.Select(d => new RecommenderItem {Id = d.Id, Entity = d});
var result =
restaurantsAsRecommenderItems
.Interleave(dishesAsRecommenderItems)
.ToList();
My recommendation would be to just make simple implicit operator :
public static implicit operator RecommenderItem(Restaurant restaurant) {
return new RecommenderItem { Id = restaurant.Id, Entity = restaurant };
}
Then you have possibility to convert these types easily like :
Restaurant rest = //...
RecommenderItem rItem = rest; // here the implicit operator is called
After doing this you can just use one for loop :
int count = Math.Max(restaurantRecommendations.Count, dishRecommendations.Count);
for ( int i = 0; i < count; i++ ) {
if ( i < restRecommendations.Count )
output.Add(restRecommendations[i]);
if ( i < dishRecommendations.Count )
output.Add(dishRecommendations[i]);
}
This will make your work much more easier.
Well, there are probably more elegant LINQ solutions but you have already most, it's also a very efficient approach:
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations, List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = Math.Min(restaurantRecommendations.Count, dishRecommendations.Count);
for (int i = 0; i < count; i++)
{
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
int remainingRestaurant = restaurantRecommendations.Count - count;
int remainingDishes = dishRecommendations.Count - count;
if (remainingRestaurant > 0)
{
for (int i = count; i < restaurantRecommendations.Count; i++)
{
var restRecommendation = restaurantRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
}
}
else if (remainingDishes > 0)
{
for (int i = count; i < dishRecommendations.Count; i++)
{
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
}
return output;
}
A simple way of doing it would be:
public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
using (var firstEnumerator = first.GetEnumerator())
using (var secondEnumerator = second.GetEnumerator())
{
while (firstEnumerator.MoveNext())
{
yield return firstEnumerator.Current;
if (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
while (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
}
After having created two arrays of restaurants and dishes of the same type RecommenderItem, you can use the Zip method like :
var restaurants = restaurantRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var dishes = dishRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var output = restaurants.Zip(dishes, (r, d) => new[] { r, d })
.SelectMany(r => r).Concat(dishes.Skip(restaurants.Length))
.Concat(restaurants.Skip(dishes.Length));
Restaraunt and Dish would have to share a base type:
restaurantRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
dishRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
Once that's the case you could use something like this slightly modified version of Zip (from System.Linq):
private static IEnumerable<T> ZipThrough<T>(IEnumerable<T> first, IEnumerable<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
using (var e1 = first.GetEnumerator())
{
using (var e2 = second.GetEnumerator())
{
while (true)
if (e1.MoveNext())
{
yield return e1.Current;
if (e2.MoveNext()) yield return e2.Current;
}
else if (e2.MoveNext())
{
yield return e2.Current;
}
else
{
break;
}
}
}
}

c# linq to sql two threads visit the same db, but one is very slow

I've created two threads in my application and the first one is the main UI thread, which will do the normal job like quering below
var order = db.Orders.Where(x=>x.systemId==id).ToList();
the other thread is checking if there is any warning situation to handle, the code is as below:
TransactionOptions transOptions = new TransactionOptions()
{ IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted };
using (new TransactionScope(TransactionScopeOption.Required, transOptions))
{
BathDBDataContext dc = new BathDBDataContext(connectionString);
var all_menus = dc.Menu.Where(x => x.addAutomatic);
var menus = all_menus.Select(x => x.name).ToList();
var orders = dc.Orders.Where(x => menus.Contains(x.menu) && !x.paid && x.deleteEmployee == null);
var add_menus = orders.Select(x => x.menu).Distinct();
var ids = orders.Select(x => x.systemId).Distinct();
foreach (var systemId in ids)
{
var seat_orders = orders.Where(x => x.systemId == systemId);
foreach (var add_menu in add_menus)
{
var add_orders = seat_orders.Where(x => x.menu == add_menu && (x.priceType == null || x.priceType != "stop counting"));
if (add_orders.Count() == 0)
continue;
var max_time = add_orders.Max(x => x.inputTime);
var max_order = add_orders.OrderByDescending(x => x.inputTime).FirstOrDefault();
//var max_order = add_orders.FirstOrDefault(x => x.inputTime == max_time);
if (max_order == null || max_order.priceType == "per hour")
continue;
var the_menu = all_menus.FirstOrDefault(x => x.name == add_menu);
string menu_time = the_menu.timeLimitHour.ToString() +
":" + the_menu.timeLimitMiniute.ToString() +
":" + the_menu.timeLimitSecond.ToString();
TimeSpan tsm = TimeSpan.Parse(menu_time);
if (DateTime.Now - max_order.inputTime < tsm)
continue;
if (the_menu.addType == "by unit")
{
Orders new_order = new Orders();
new_order.menu = max_order.menu;
new_order.text = max_order.text;
new_order.systemId = systemId;
new_order.number = 1;
new_order.money = the_menu.price;
new_order.technician = max_order.technician;
new_order.techType = max_order.techType;
new_order.inputTime = DateTime.Now;
new_order.inputEmployee = "computer";
new_order.paid = false;
dc.Orders.InsertOnSubmit(new_order);
}
else if (the_menu.addType == "by time")
{
Orders new_order = new Orders();
new_order.menu = max_order.menu;
new_order.text = max_order.text;
new_order.systemId = systemId;
new_order.number = 1;
new_order.priceType = "per hour";
new_order.money = Convert.ToDouble(the_menu.addMoney);
new_order.technician = max_order.technician;
new_order.techType = max_order.techType;
new_order.inputTime = DateTime.Now;
new_order.inputEmployee = "computer";
new_order.paid = false;
dc.Orders.InsertOnSubmit(new_order);
}
}
dc.SubmitChanges();
}
in this situation, the query in the main UI thread will be very slow or sometimes doesn't even work.
can anyone help me with that?
Correct me if I am wrong but you seem to insert values to Orders in the loop and also trying to use an expression in your main UI.
Besides 5 seconds interval might be short since your LINQ expressions get more complex so that time needed for the calculations increases.
Loading that "BathDBDataContext" might take more time as your database gets new records.
I'd suggest using Cache and increase 5 seconds refresh timer.

Static methods getting slower in when accesses by multiple threads

I have a situation where my C# application with .Net 4 is accessing a static method within a static class making the method perform slower than when accessed by a single thread. Say for example, If the method is called by a single thread it takes around 1.5 minutes to complete, whereas when called by 2 threads it takes 4 minutes to complete.
Any ideas/suggestion/best practices to increase the performance is highly appreciated. Thanks in advance for your responses.
Some more info:
This is an application which was using threadpool earlier to perform threading in some parts. I introduced parallel tasks. Hence it's a mix up of TPL and older Thread pool. Say for example one parallel task can have multiple threads. The machine has 24 CPU cores with 64GB RAM. I ran 2 processes which would have split to 5 threads each totalling upto 10 threads. During this the process got slower. I am pasting the code here for those who would like to inspect it and provide suggestions. Sorry for pasting long code. The code may not be having all current latest features as this was coded few years ago. Thanks once again.
public static class Property11
{
/// <summary>
/// Splits agg rows to separate commands where the reference parameters are included for each command.
/// </summary>
/// <param name="worksheetID">The current worksheet ID.</param>
/// <param name="propertyID">The current property ID.</param>
/// <param name="ccrFormObj">The ccr form object.</param>
public static void SplitAggregateIncludeReferenceParameterCCRToDTH(PropertyCall propertyCallObj)
{
string worksheetID = propertyCallObj.WorksheetID;
int propertyID = propertyCallObj.PropertyID;
IDClass nextIDObj = propertyCallObj.NextIDObj;
CCRFormStructure ccrFormObj = propertyCallObj.CCRFormObj;
List<CCRFormStructure> ccrFormObjsToAdd = propertyCallObj.CCRFormObjToAddList;
DateTime dtProp = DateTime.Now;
System.Diagnostics.Debug.Print("Start time property = " + propertyCallObj.PropertyID + ", worksheet = " + propertyCallObj.WorksheetID + ": " + dtProp.ToString());
try
{
// Get all rows for worksheet
List<WorksheetRow> rowListForWorksheet =
(from wr in ccrFormObj.myWorksheetRowList
where wr.WorksheetID == worksheetID
select wr).ToList();
// Get all parameters for worksheet
List<WorksheetRowParameter> rowParameterListForWorksheet =
(from wrp in ccrFormObj.myWorksheetRowParameterList
join wr in rowListForWorksheet
on wrp.WorksheetRowID equals wr.ID
select wrp).ToList();
// Get all agg rows in worksheet
List<AggRow> aggRowsInWorksheet =
(from ar in ccrFormObj.myAggRowList
join wsrp in rowParameterListForWorksheet
on ar.WorksheetRowParameterID equals wsrp.ID
select ar).ToList();
// Get all agg row parameters in worksheet
List<AggRowParameter> aggParametersInWorksheet =
(from arp in ccrFormObj.myAggRowParameterList
join ar in aggRowsInWorksheet
on arp.AggRowID equals ar.ID
select arp).ToList();
// Get all command mappings for worksheet
List<CommandMappingObj> commandMappingListForWorksheet =
(from cm in ccrFormObj.commandMappingList
join wr in rowListForWorksheet
on cm.WorksheetRowID equals wr.ID
select cm).ToList();
// Get all parameter mappings for worksheet
List<ParameterMappingObj> parameterMappingListForWorksheet =
(from pm in ccrFormObj.parameterMappingList
join cm in commandMappingListForWorksheet
on pm.CommandMappingObjID equals cm.ID
select pm).ToList();
// Get all property objects for worksheet
List<ParameterPropertyObj> propertyList =
(from ppo in ccrFormObj.parameterPropertiesList
where ppo.ID == propertyID && ppo.WorksheetID == worksheetID
select ppo).ToList();
//List<WorksheetRow> rowsToRemove = new List<WorksheetRow>();
WorksheetRowParameter currentWorksheetRowParameter;
AggRow currentAggRow;
AggRowParameter currentAggRowParameter;
AggRow currentSteeringAggRow;
AggRowParameter currentSteeringAggRowParameter;
int newIDIndex = 0;
List<string> worksheetRowsWithoutTooLongCommandRows = new List<string>();
WorksheetRow newWSR = new WorksheetRow();
CommandMappingObj newCMO = new CommandMappingObj();
WorksheetRow newWSRForOrigRow = new WorksheetRow();
CommandMappingObj newChangeCMO = new CommandMappingObj();
List<string> steeringParameters;
IEnumerable<WorksheetRowParameter> parameterListForRow;
IEnumerable<WorksheetRowParameter> currentSteeringParameters;
string newCMOID;
ParameterMappingObj newPMO;
WorksheetRowParameter newWSRP;
string newWSRID;
string newID;
IEnumerable<string> commandsWithPropertyParameterForRow;
Hashtable htPropertyParamAndSteeringParameters = new Hashtable();
List<string> steeringParametersForProperty;
WorksheetRowParameter currentWorksheetRowPropertyParameter;
bool removeOrigRow = false;
bool firstRowForAggCreated = false;
List<WorksheetRowParameter> propParamListForFirstCreatedRow = new List<WorksheetRowParameter>();
List<string> propParamUsedAsSteeringList = new List<string>();
foreach (ParameterPropertyObj propertyParameter in propertyList)
{
if (propertyParameter.SecondaryPropertyInfo != null && propertyParameter.SecondaryPropertyInfo != "")
{
steeringParameters = propertyParameter.SecondaryPropertyInfo.Split(",".ToCharArray()).ToList();
}
else
{
steeringParameters = new List<string>();
}
htPropertyParamAndSteeringParameters.Add(propertyParameter.Parameter, steeringParameters);
}
var aggListForRow =
from ar in aggRowsInWorksheet
join arp in aggParametersInWorksheet
on ar.ID equals arp.AggRowID
select new
{
AggRow = ar,
AggRowParameter = arp
};
var worksheetRowsWithRepParam =
from wrp in rowParameterListForWorksheet
where htPropertyParamAndSteeringParameters.Contains(wrp.Parameter)
join al in aggListForRow
on wrp.ID equals al.AggRow.WorksheetRowParameterID
into aggList
where aggList.Count() > 0
select new
{
WorksheetRowParameter = wrp,
AggList = aggList
};
foreach (WorksheetRow worksheetRow in rowListForWorksheet.ToList())
{
var worksheetRowWithRepParam =
worksheetRowsWithRepParam.Where(wrp => wrp.WorksheetRowParameter.WorksheetRowID == worksheetRow.ID);
if (worksheetRowWithRepParam.Count() > 0)
{
firstRowForAggCreated = false;
var currentMappingList =
from cmo in commandMappingListForWorksheet
where cmo.WorksheetRowID == worksheetRow.ID
join pmo in parameterMappingListForWorksheet
on cmo.ID equals pmo.CommandMappingObjID
into parameterMappingList
select new
{
CommandMapping = cmo,
ParameterMappingList = parameterMappingList
};
IEnumerable<ParameterPropertyObj> sortedPropertyList =
from wrwrp in worksheetRowWithRepParam
join ppo in propertyList
on wrwrp.WorksheetRowParameter.Parameter equals ppo.Parameter
orderby wrwrp.AggList.Count() descending
select ppo;
propParamUsedAsSteeringList.Clear();
foreach (ParameterPropertyObj ppo in sortedPropertyList)
{
if (!propParamUsedAsSteeringList.Contains(ppo.Parameter))
{
var currentWorksheetRowsWithRepParam =
worksheetRowWithRepParam.Where(p => p.WorksheetRowParameter.Parameter == ppo.Parameter);
if (currentWorksheetRowsWithRepParam.Count() == 0)
{
continue;
}
var currentWorksheetRowWithRepParam = currentWorksheetRowsWithRepParam.ElementAt(0);
var currentAggList = currentWorksheetRowWithRepParam.AggList;
if (!firstRowForAggCreated)
{
currentWorksheetRowPropertyParameter = currentWorksheetRowWithRepParam.WorksheetRowParameter;
}
else
{
currentWorksheetRowPropertyParameter = propParamListForFirstCreatedRow.Where(p => p.Parameter == ppo.Parameter).ElementAt(0);
}
if (currentAggList.Count() > 1)
{
removeOrigRow = true;
steeringParametersForProperty = (List<string>)htPropertyParamAndSteeringParameters[ppo.Parameter];
currentSteeringParameters =
from wrp in rowParameterListForWorksheet
where wrp.WorksheetRowID == worksheetRow.ID
&& steeringParametersForProperty.Contains(wrp.Parameter)
select wrp;
commandsWithPropertyParameterForRow =
from cml in currentMappingList
where cml.ParameterMappingList.Count(pmo => pmo.Name == ppo.Parameter) > 0
select cml.CommandMapping.Name;
propParamUsedAsSteeringList.AddRange(
from sp in sortedPropertyList
where sp.Parameter != ppo.Parameter
join csp in currentSteeringParameters
on sp.Parameter equals csp.Parameter
select csp.Parameter);
// CREATE NEW WORKSHEET ROWS FOR EACH BUT THE FIRST AGG ROW PARAMETER
for (int i = 0; i < currentAggList.Count(); i++)
{
currentAggRow = currentAggList.ElementAt(i).AggRow;
currentAggRowParameter = currentAggList.ElementAt(i).AggRowParameter;
if (i == 0)
{
currentWorksheetRowPropertyParameter.Value = currentAggRowParameter.Value;
if (!firstRowForAggCreated)
{
propParamListForFirstCreatedRow.Clear();
newWSRID = newIDIndex.ToString().PadLeft(3, '0');
newID = newWSRID;
if (!worksheetRow.ID.Contains(','))
{
newID = "," + newWSRID;
}
newWSRForOrigRow = new WorksheetRow
{
ID = worksheetRow.ID + newID,
OriginalWorksheetRowID = worksheetRow.OriginalWorksheetRowID,
WorksheetID = worksheetRow.WorksheetID
};
ccrFormObj.myWorksheetRowList.Add(newWSRForOrigRow);
parameterListForRow =
from wrp in rowParameterListForWorksheet
where wrp.WorksheetRowID == worksheetRow.ID
select wrp;
foreach (WorksheetRowParameter currentParameter in parameterListForRow)
{
newID = "";
if ((currentParameter.ID != null) && (!currentParameter.ID.Contains(',')))
{
newID = ",";
}
newID += newIDIndex.ToString().PadLeft(3, '0');
newWSRP = new WorksheetRowParameter
{
ID = currentParameter.ID + newID,
OriginalParameterID = currentParameter.OriginalParameterID,
WorksheetRowID = newWSRForOrigRow.ID,
Parameter = currentParameter.Parameter,
Value = currentParameter.Value,
Disabled = currentParameter.Disabled
};
if (htPropertyParamAndSteeringParameters.Contains(newWSRP.Parameter)
&& newWSRP.Parameter != ppo.Parameter)
{
// TODO: IF AGG, TAKE AGG POS VALUE
var steeringParamAggList =
from wrwrp in worksheetRowWithRepParam
where wrwrp.WorksheetRowParameter.Parameter == newWSRP.Parameter
select wrwrp.AggList;
if (steeringParamAggList.Count() > 0)
{
if (steeringParamAggList.ElementAt(0).Count() > i)
{
currentSteeringAggRow = steeringParamAggList.ElementAt(0).ElementAt(i).AggRow;
currentSteeringAggRowParameter = steeringParamAggList.ElementAt(0).ElementAt(i).AggRowParameter;
newWSRP.Value = currentSteeringAggRowParameter.Value;
ccrFormObj.myAggRowParameterList.Remove(currentSteeringAggRowParameter);
ccrFormObj.myAggRowList.Remove(currentSteeringAggRow);
ccrFormObj.myWorksheetRowParameterList.Add(newWSRP);
}
}
else
{
ccrFormObj.myWorksheetRowParameterList.Add(newWSRP);
}
propParamListForFirstCreatedRow.Add(newWSRP);
}
else
{
ccrFormObj.myWorksheetRowParameterList.Add(newWSRP);
}
}
foreach (var currentMapping in currentMappingList)
{
// Re-point original command mapping to new row
newCMOID = newIDIndex.ToString().PadLeft(3, '0');
if (!currentMapping.CommandMapping.ID.Contains(','))
{
newID = "," + newCMOID;
}
// Create new command mapping object
newCMO = new CommandMappingObj
{
ID = currentMapping.CommandMapping.ID + newID,
Name = currentMapping.CommandMapping.Name,
WorksheetRowID = newWSRForOrigRow.ID
};
ccrFormObj.commandMappingList.Add(newCMO);
foreach (ParameterMappingObj pmo in currentMapping.ParameterMappingList)
{
newPMO = new ParameterMappingObj
{
Name = pmo.Name,
CommandMappingObjID = newCMO.ID
};
ccrFormObj.parameterMappingList.Add(newPMO);
}
}
firstRowForAggCreated = true;
}
}
else
{
newWSRID = newIDIndex.ToString().PadLeft(3, '0');
newID = newWSRID;
if (!worksheetRow.ID.Contains(','))
{
newID = "," + newWSRID;
}
newWSR = new WorksheetRow
{
ID = worksheetRow.ID + newID,
OriginalWorksheetRowID = worksheetRow.OriginalWorksheetRowID,
WorksheetID = worksheetRow.WorksheetID
};
ccrFormObj.myWorksheetRowList.Add(newWSR);
foreach (WorksheetRowParameter currentSteeringParameter in currentSteeringParameters)
{
newID = "";
if ((currentSteeringParameter.ID != null) && (!currentSteeringParameter.ID.Contains(',')))
{
newID = ",";
}
newID += newIDIndex.ToString().PadLeft(3, '0');
newWSRP = new WorksheetRowParameter
{
ID = currentSteeringParameter.ID + newID,
OriginalParameterID = currentSteeringParameter.OriginalParameterID,
WorksheetRowID = newWSR.ID,
Parameter = currentSteeringParameter.Parameter,
Value = currentSteeringParameter.Value,
Disabled = currentSteeringParameter.Disabled
};
var steeringParamAggList =
from wrwrp in worksheetRowWithRepParam
where wrwrp.WorksheetRowParameter.Parameter == newWSRP.Parameter
select wrwrp.AggList;
if (steeringParamAggList.Count() > 0)
{
if (steeringParamAggList.ElementAt(0).Count() > i)
{
currentSteeringAggRow = steeringParamAggList.ElementAt(0).ElementAt(i).AggRow;
currentSteeringAggRowParameter = steeringParamAggList.ElementAt(0).ElementAt(i).AggRowParameter;
newWSRP.Value = currentSteeringAggRowParameter.Value;
ccrFormObj.myAggRowParameterList.Remove(currentSteeringAggRowParameter);
ccrFormObj.myAggRowList.Remove(currentSteeringAggRow);
ccrFormObj.myWorksheetRowParameterList.Add(newWSRP);
}
}
else
{
ccrFormObj.myWorksheetRowParameterList.Add(newWSRP);
}
}
// Add rep param
newID = "";
if ((currentWorksheetRowPropertyParameter.ID != null) && (!currentWorksheetRowPropertyParameter.ID.Contains(',')))
{
newID = ",";
}
newID += newIDIndex.ToString().PadLeft(3, '0');
newWSRP = new WorksheetRowParameter
{
ID = currentWorksheetRowPropertyParameter.ID + newID,
OriginalParameterID = currentWorksheetRowPropertyParameter.OriginalParameterID,
WorksheetRowID = newWSR.ID,
Parameter = currentWorksheetRowPropertyParameter.Parameter,
Value = currentAggRowParameter.Value,
Disabled = currentWorksheetRowPropertyParameter.Disabled
};
ccrFormObj.myWorksheetRowParameterList.Add(newWSRP);
foreach (var currentMapping in currentMappingList)
{
if (commandsWithPropertyParameterForRow.Contains(currentMapping.CommandMapping.Name))
{
newCMOID = newIDIndex.ToString().PadLeft(3, '0');
if (!currentMapping.CommandMapping.ID.Contains(','))
{
newID = "," + newCMOID;
}
// Create new command mapping object
newCMO = new CommandMappingObj
{
ID = currentMapping.CommandMapping.ID + newID,
Name = currentMapping.CommandMapping.Name,
WorksheetRowID = newWSR.ID
};
ccrFormObj.commandMappingList.Add(newCMO);
foreach (ParameterMappingObj pmo in currentMapping.ParameterMappingList)
{
if ((pmo.Name == ppo.Parameter) || (currentSteeringParameters.Count(p => p.Parameter == pmo.Name) > 0))
{
newPMO = new ParameterMappingObj
{
Name = pmo.Name,
CommandMappingObjID = newCMO.ID
};
ccrFormObj.parameterMappingList.Add(newPMO);
}
}
}
}
}
newIDIndex++;
ccrFormObj.myAggRowParameterList.Remove(currentAggRowParameter);
ccrFormObj.myAggRowList.Remove(currentAggRow);
}
}
else
{
currentAggRow = currentAggList.ElementAt(0).AggRow;
currentAggRowParameter = currentAggList.ElementAt(0).AggRowParameter;
currentWorks
There is nothing intrinsic about a static method that would make it slower when accessed by multiple threads, unless for example:
you have some shared resource that is being a throttle, such as a shared lock, or going to a single UI thread
you have more threads than available CPU cores, and it is simply having to do lots of switching
you are maxing out some other resource, such as disk IO, network IO, etc
Since you haven't posted any code, we can only guess.

Determining value jumps in List<T>

I have a class:
public class ShipmentInformation
{
public string OuterNo { get; set; }
public long Start { get; set; }
public long End { get; set; }
}
I have a List<ShipmentInformation> variable called Results.
I then do:
List<ShipmentInformation> FinalResults = new List<ShipmentInformation>();
var OuterNumbers = Results.GroupBy(x => x.OuterNo);
foreach(var item in OuterNumbers)
{
var orderedData = item.OrderBy(x => x.Start);
ShipmentInformation shipment = new ShipmentInformation();
shipment.OuterNo = item.Key;
shipment.Start = orderedData.First().Start;
shipment.End = orderedData.Last().End;
FinalResults.Add(shipment);
}
The issue I have now is that within each grouped item I have various ShipmentInformation but the Start number may not be sequential by x. x can be 300 or 200 based on a incoming parameter. To illustrate I could have
Start = 1, End = 300
Start = 301, End = 600
Start = 601, End = 900
Start = 1201, End = 1500
Start = 1501, End = 1800
Because I have this jump I cannot use the above loop to create an instance of ShipmentInformation and take the first and last item in orderedData to use their data to populate that instance.
I would like some way of identifying a jump by 300 or 200 and creating an instance of ShipmentInformation to add to FinalResults where the data is sequnetial.
Using the above example I would have 2 instances of ShipmentInformation with a Start of 1 and an End of 900 and another with a Start of 1201 and End of 1800
Try the following:
private static IEnumerable<ShipmentInformation> Compress(IEnumerable<ShipmentInformation> shipments)
{
var orderedData = shipments.OrderBy(s => s.OuterNo).ThenBy(s => s.Start);
using (var enumerator = orderedData.GetEnumerator())
{
ShipmentInformation compressed = null;
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (compressed == null)
{
compressed = current;
continue;
}
if (compressed.OuterNo != current.OuterNo || compressed.End < current.Start - 1)
{
yield return compressed;
compressed = current;
continue;
}
compressed.End = current.End;
}
if (compressed != null)
{
yield return compressed;
}
}
}
Useable like so:
var finalResults = Results.SelectMany(Compress).ToList();
If you want something that probably has terrible performance and is impossible to understand, but only uses out-of-the box LINQ, I think this might do it.
var orderedData = item.OrderBy(x => x.Start);
orderedData
.SelectMany(x =>
Enumerable
.Range(x.Start, 1 + x.End - x.Start)
.Select(n => new { time = n, info = x))
.Select((x, i) => new { index = i, time = x.time, info = x.info } )
.GroupBy(t => t.time - t.info)
.Select(g => new ShipmentInformation {
OuterNo = g.First().Key,
Start = g.First().Start(),
End = g.Last().End });
My brain hurts.
(Edit for clarity: this just replaces what goes inside your foreach loop. You can make it even more horrible by putting this inside a Select statement to replace the foreach loop, like in rich's answer.)
How about this?
List<ShipmentInfo> si = new List<ShipmentInfo>();
si.Add(new ShipmentInfo(orderedData.First()));
for (int index = 1; index < orderedData.Count(); ++index)
{
if (orderedData.ElementAt(index).Start ==
(si.ElementAt(si.Count() - 1).End + 1))
{
si[si.Count() - 1].End = orderedData.ElementAt(index).End;
}
else
{
si.Add(new ShipmentInfo(orderedData.ElementAt(index)));
}
}
FinalResults.AddRange(si);
Another LINQ solution would be to use the Except extension method.
EDIT: Rewritten in C#, includes composing the missing points back into Ranges:
class Program
{
static void Main(string[] args)
{
Range[] l_ranges = new Range[] {
new Range() { Start = 10, End = 19 },
new Range() { Start = 20, End = 29 },
new Range() { Start = 40, End = 49 },
new Range() { Start = 50, End = 59 }
};
var l_flattenedRanges =
from l_range in l_ranges
from l_point in Enumerable.Range(l_range.Start, 1 + l_range.End - l_range.Start)
select l_point;
var l_min = 0;
var l_max = l_flattenedRanges.Max();
var l_allPoints =
Enumerable.Range(l_min, 1 + l_max - l_min);
var l_missingPoints =
l_allPoints.Except(l_flattenedRanges);
var l_lastRange = new Range() { Start = l_missingPoints.Min(), End = l_missingPoints.Min() };
var l_missingRanges = new List<Range>();
l_missingPoints.ToList<int>().ForEach(delegate(int i)
{
if (i > l_lastRange.End + 1)
{
l_missingRanges.Add(l_lastRange);
l_lastRange = new Range() { Start = i, End = i };
}
else
{
l_lastRange.End = i;
}
});
l_missingRanges.Add(l_lastRange);
foreach (Range l_missingRange in l_missingRanges) {
Console.WriteLine("Start = " + l_missingRange.Start + " End = " + l_missingRange.End);
}
Console.ReadKey(true);
}
}
class Range
{
public int Start { get; set; }
public int End { get; set; }
}

Categories