-1

While using a global integer named GlobalID that I use as a counter, the following code works fine:

MyFunction(myClass thisClass, Int ID)
{   
     ListOfMyClass.add(thisClass)

     If (thisClass.IsPropertyValueAboveZero)
     {
          ID = GlobalID;
     }

     GlobalID++;

     Foreach (class someClass in ListOfClasses)
     {
           MyClass newClass = new MyClass(GlobalID, ID)
           MyFunction(newClass, ID)  //Recursive Call
     }
 }

I'm basically using GlobalID as a incrementing counter for each time the function is called. The counter is assigned a starting position for the first time it executes. I'm using a global variable because I wanted to make sure the ID is accurately increased for each pass regardless of execution entering or leaving the recursive call. This function is called (the first time....) from a ForEach loop that assigns the start position of global variable

My goal is to use a Parallel.ForEach for the initial call instead of the regular For Each loop. My problem deals with the counter. I don't know how to manage that counter within multiple threads. If I pass it as a variable to the function, I believe I will have a inaccurate lower / used number leaving the recursive loop. The global variable ensures the next number is higher than the previous number. The thisClass.IsPropertyValueAboveZero is just a arbitrary means of describing action based on a conditional statement. It has no meaningful reference to the rest of the code.

If I have multiple threads that have different starting positions for their counter, how do I accomplish making this thread safe? The only way I see at the moment is manually writing multiple versions of the same function and counter and using TaskFactory

Jason R.
  • 379
  • 1
  • 6
  • 19
  • I seen `Interlocked.Increment`, eventually I will need that but the problem is having multiple instances of that counter and making sure it increases for each pass especially when leaving the recursion is issue – Jason R. Sep 15 '17 at 13:50
  • @mjwills That will prevent race conditions, but will not help you if you want the threads to maintain independent counters that may be offset from each other. – Asad Saeeduddin Sep 15 '17 at 13:51
  • `Interlocked.Increment` is fine for incrementing `GlobalID`, but presumably you don't want duplicates on this line: `ID = GlobalID;`, so you will be to lock around that whole block. – Matt Burland Sep 15 '17 at 13:52
  • @JasonR. Assuming this work is batched and not continuously incoming, you can maintain a separate counter per thread, and aggregate the counts at the point where you join all the threads. – Asad Saeeduddin Sep 15 '17 at 13:54
  • @mjwills, I'm okay with separate counters, but if I have separate counters, won't that force difference functions too? – Jason R. Sep 15 '17 at 13:55
  • @JasonR. My answer (below) assumes you want a single counter. But Asad (above) has got me wondering whether I have misunderstood the problem. Does my answer (below) do what you need? – mjwills Sep 15 '17 at 13:56
  • @AsadSaeeduddin, I don't I understand the 'aggregate the counts' comment. The counter is simply used just like a RowID in SQL. But with the recursion, if I pass the "RowID" as a parameter, the first pass after leaving the recursive call will be a used number – Jason R. Sep 15 '17 at 13:58
  • @mjwills, No. It only solves part of the problem which was not asked that deals with how to increment in a thread safe way. That information is appreciated, but I understand that part. Its maintaining a incrementing value while leaving the recursion, and how to manage it in a thread safe way. I think someone suggested multiple counters, but I think that forces multiple instances of the function too. So far, I think that is the only way – Jason R. Sep 15 '17 at 14:01
  • @mjwills, but its more than that. Each thread will have a starting position. Its not necessarily counting the calls, its more like assigning a Primary Key, but in c#. – Jason R. Sep 15 '17 at 14:02
  • Can you update your posts to show some sample inputs and expected outputs @JasonR. ? – mjwills Sep 15 '17 at 14:03
  • Had to be more than just up of https://stackoverflow.com/questions/13181740/c-sharp-thread-safe-fastest-counter as value of the counter is used in the code (closed but than reopened) – Alexei Levenkov Sep 15 '17 at 14:29
  • Note that sample code is very confusing - it is absolutely not clear what your expectation for relationship between `ID` and `GlobalID` passed to `new MyClass(GlobaID, ID)`. Especially in multithreaded case where you can't read value of `GlobalID` at all. – Alexei Levenkov Sep 15 '17 at 14:47
  • @AlexeiLevenkov The sample code is not meant to thread safe, it was a example of how current code works. Multiple things would need to change to make it thread safe, the question was directed at the parts that I didn't know how to complete. – Jason R. Sep 15 '17 at 15:44
  • 1
    @JasonR. it is very hard to suggest correct implementation if you don't know what you actually want to achieve (and thus it makes post less suitable for SO, also it can be answered in a way that answers title and may not be helpful to your case - which is somewhat what happened)... Ryan's and my answer and it should show you problems you going to face but I don't think either of our answers are directly applicable to your sample (also both answer the question in the title). – Alexei Levenkov Sep 15 '17 at 16:15

3 Answers3

4

For thread-safe counting, use the Interlocked methods: System.Threading.Interlocked.Increment(ref globalID)

private static int globalID;

// Be very careful about using a property to expose the counter
// do not *use* this value directly, but can be useful for debugging
// instead use the return values from Increment, Decrement, etc.
public int UnsafeGlobalID { get { return globalID; } }

Note: you need to use a field for the ref. It is possible to expose the field through a property, but can be problematic in code. It is better to use Interlock methods such as Interlocked.CompareExchange or explicit lock statements around logic requiring the value in a synchronized manner. The return value for Increment, Decrement, and so on is usually what should be used inside logic.

My problem deals with the counter. I don't know how to manage that counter within multiple threads. If I pass it as a variable to the function, I believe I will have a inaccurate lower / used number leaving the recursive loop. The global variable ensures the next number is higher than the previous number.

If I have multiple threads that have different starting positions for their counter, how do I accomplish making this thread safe? The only way I see at the moment is manually writing multiple versions of the same function and counter and using TaskFactory

The recursive call to MyFunction(newClass, ID) will have the value of ID, but I'm not sure what If (thisClass.IsPropertyValueAboveZero) is supposed to do though. Is it to make sure you have a non-zero starting point? If so, it would be better to make sure it is non-zero before the initial call outside of this function.

Also the logic in the foreach loop doesn't make sense to me. In MyClass newClass = new MyClass(GlobalID, ID) the ID will be argument value, or if IsPropertyValueAboveZero is true it will be the current value of GlobalID. So ID will typically be less than GlobalID since GlobalID is incremented before the foreach loop. I think you are passing GlobalID when you do not need to.

// if IsPropertyValueAboveZero is intended to start above zero
// then you can just initialize the counter to 1
private static int globalID = 1;

public void MyFunction(myClass thisClass, int id)
{
    // ListOfMyClass and ListOfClasses are probably not thread-safe
    // and you may need to add locks around the Add and get a copy
    // of ListOfClasses before the foreach enumeration
    // You may want to look at https://stackoverflow.com/a/6601832/29762
    ListOfMyClass.Add(thisClass); 

    foreach (class someClass in ListOfClasses)
    {
        int newId = System.Threading.Interlocked.Increment(ref globalID);
        MyClass newClass = new MyClass(newId);
        MyFunction(newClass, newId);  //Recursive Call
    }
}
Community
  • 1
  • 1
Ryan
  • 7,835
  • 2
  • 29
  • 36
  • Answer has some good points, but "you can expose the field through a property" is awful suggestion - you can as well use `public int GlobalID { get { return random.Next(1000); } }` instead. There is no way to get value of the variable that makes sense while multiple threads are incrementing it. – Alexei Levenkov Sep 15 '17 at 14:49
  • the `(thisClass.IsPropertyValueAboveZero)` is just a arbitrary means of saying based on a conditional statement. It has no meaningful reference to the rest of the code. I will update the question to reflect this – Jason R. Sep 15 '17 at 14:52
  • @AlexeiLevenkov you have a good point about the return value of the field/property. However, it is the value at that moment in time. You should never *use* the value in deterministic code (use one of the Interlocked methods instead e.g. `Interlocked.CompareExchange`). I have found it to be useful sometimes in debugging to get an idea of relative values and what is going on. As a side note, the `get` for `Int32` is thread-safe but for `Int64` you need to use `Interlocked.Read`. I've updated my answer based on your comment. – Ryan Sep 15 '17 at 15:17
  • @Ryan like the edit. `CompareExchange` does not help much either got get *useful* value - as you pointed out it is "value at that moment in time" which is as good as random value. (Indeed readonly property for debugging is useful, but it's way too tempting to start using it in code... maybe some scarier property name would help) – Alexei Levenkov Sep 15 '17 at 15:27
  • @JasonR. the logic in the if conditional `IsPropertyValueAboveZero` is important because you are trying to use the current value of `GlobalID` in the block and that is exactly what @AlexeiLevenkov warned against. It is not safe to use GlobalID in that manner and you would need additional locking logic. – Ryan Sep 15 '17 at 15:33
2

If you use variable just as a counter standard usage of Interlocked.Increment works (see C# Thread safe fast(est) counter). Since your code actually wants to use the value of the counter you must be very careful to get correct value as reading from the value must be protected at the same time.

Since you only need to read value when you increment it than Interlocked.Increment is still enough. Be sure to never check or use value or GlobalID directly while multiple threads can modify it:

var ID = someValue; 
var newValue = Interlocked.Increment(ref GlobalID);
if (thisClass.IsPropertyValueAboveZero)
{
      // you can't use GlobalID directly here because it could be already incremented more
      ID = newValue - 1; // note -1 to match your original code.

}

Otherwise code get much trickier - for example if you only sometimes want to increment the global value but use it for every call (which probably not your case as sample shows incrementing always using less often) than you must pass current value to your function separately. Also in this case value passed to function and actual counter will have nothing to do with each other. If use case is anything other than "use value only when incremented" you probably should review what you are trying to achieve - most likely you would need some other data structure. Note that using lock to just protect reads and writes to counter not going to solve thread safety as you can't predict value you are about to read (may be incremented multiple times by other threads).

Note: sample code in the post shows some unclear usage of some old ID value and latest counter value. If code actually reflects what you want to do separating "counting number of calls" from "object ID" would be useful.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
-1

For thread-safe integer incrementing of a single counter, consider using Interlocked.Increment.

Store the variable in a static:

public static int Bob;

Then increment it in a static function:

public static int IncrementBob()
{
    return Interlocked.Increment(ref Bob);
}

Anytime you want to increment, call IncrementBob.

mjwills
  • 23,389
  • 6
  • 40
  • 63
  • 1
    What about Bob? (Sorry, couldn't resist. Look here for those who do not know about the movie: http://www.imdb.com/title/tt0103241/ ) – Ryan Sep 15 '17 at 16:36