Multithreading, lambda and local variables

My question is that in the code below, can I be sure that the instance methods will access variables that I think they will or can be changed by another thread while I am still working? Closures have anything with this, i.e. I will be working on a local copy of IEnumerable<T> , so is the enumeration safe?

To rephrase my question, do I need any locks if I never write about shared variables?

 public class CustomerClass { private Config cfg = (Config)ConfigurationManager.GetSection("Customer"); public void Run() { var serviceGroups = this.cfg.ServiceDeskGroups.Select(n => n.Group).ToList(); var groupedData = DataReader.GetSourceData().AsEnumerable().GroupBy(n => n.Field<int>("ID")); Parallel.ForEach<IGrouping<int, DataRow>, CustomerDataContext>( groupedData, () => new CustomerDataContext(), (g, _, ctx) => { var inter = this.FindOrCreateInteraction(ctx, g.Key); inter.ID = g.Key; inter.Title = g.First().Field<string>("Title"); this.CalculateSomeProperty(ref inter, serviceGroups); return ctx; }, ctx => ctx.SubmitAllChanges()); } private Interaction FindOrCreateInteraction(CustomerDataContext ctx, int ID) { var inter = ctx.Interactions.Where(n => n.Id = ID).SingleOrDefault(); if (inter == null) { inter = new Interaction(); ctx.InsertOnSubmit(inter); } return inter; } private void CalculateSomeProperty(ref Interaction inter, IEnumerable<string> serviceDeskGroups) { // Reads from the List<T> class instance variable. Changes the state of the ref'd object. if (serviceGroups.Contains(inter.Group)) { inter.Ours = true; } } } 
+7
source share
2 answers

It seems that I found the answer and the question is also in this process.

The real question was whether it is possible to trust local "variables", which are actually objects, for simultaneous access. The answer is no, if they have an internal state that is not processed in a thread-safe manner, all bets are disabled. Closing does not help, it just captures the link to the specified object.

In my specific case - reading from IEnumerable<T> at the same time and not writing to it, it is actually thread safe, because every call to foreach , Contains() , Where() , etc. gets a new new IEnumerator , which is displayed only from the thread that requested it. However, any other objects must also be checked one by one.

So cheers, no locks or synchronized collections for me :)

Thanks to @ebb and @Dave, although you did not answer the question directly, you pointed me in the right direction.


If you are interested in the results, this is the launch on my home PC (quad) using Thread.SpinWait to simulate the string processing time. The real application improved by almost 2X (01:03 vs. 00:34) on a dual-core hyper-threaded machine with SQL Server on the local network.

Singlethreaded Single threaded using foreach . I don’t know why, but there are a fairly large number of contextual context switches.

Multithreaded Using Parallel.ForEach , do not require locking with thread locators where necessary.

+3
source

Now, from what I can say, your instance methods do not use any member variables. This makes them standstill and therefore thread safe. However, in this case, you would be better off labeling them β€œstatic” for code clarity and a small performance gain.

If these instance methods used a member variable, then they would only be as thread safe as this variable (for example, if you used a simple list, it would not be thread safe, and you might see strange behavior). In short, member variables are opposed to easy thread safety.

Here is my refactor (disclaimer, not verified). If you want to provide the data that was transferred, you will remain calmer if you pass it as parameters and do not save them as member variables:

UPDATE: you asked for a way to reference your read-only list, so I added this and removed the static tags (so that the instance variable can be split).

 public class CustomerClass { private List<string> someReadOnlyList; public CustomerClass(){ List<string> tempList = new List<string>() { "string1", "string2" }; someReadOnlyList = ArrayList.Synchronized(tempList); } public void Run() { var groupedData = DataReader.GetSourceData().AsEnumerable().GroupBy(n => n.Field<int>("ID")); Parallel.ForEach<IGrouping<int, DataRow>, CustomerDataContext>( groupedData, () => new CustomerDataContext(), (g, _, ctx) => { var inter = FindOrCreateInteraction(ctx, g.Key); inter.ID = g.Key; inter.Title = g.First().Field<string>("Title"); CalculateSomeProperty(ref inter); return ctx; }, ctx => ctx.SubmitAllChanges()); } private Interaction FindOrCreateInteraction(CustomerDataContext ctx, int ID) { var query = ctx.Interactions.Where(n => n.Id = ID); if (query.Any()) { return query.Single(); } else { var inter = new Interaction(); ctx.InsertOnSubmit(inter); return inter; } } private void CalculateSomeProperty(ref Interaction inter) { Console.Writeline(someReadOnlyList[0]); //do some other stuff } } 
+1
source

All Articles